第三十 非对称密码算法
非对称密码算法(Asymmetric Cryptography),又称公钥密码学(Public-Key Cryptography),是现代信息安全的基石。与对称密码使用同一把密钥进行加密和解密不同,非对称密码使用一对数学上关联的密钥——公钥和私钥。这一突破性的概念由Whitfield Diffie和Martin Hellman于1976年提出,彻底改变了密码学的面貌。
非对称密码概述
对称密码 vs 非对称密码
在对称密码体系中,加密和解密使用同一把密钥,密钥分发是其最大的难题——如何在不安全的信道上安全地传递密钥?非对称密码正是为解决这一问题而诞生的。
| 对比维度 | 对称密码 | 非对称密码 |
|---|---|---|
| 密钥数量 | 一把密钥(加密和解密共用) | 一对密钥(公钥 + 私钥) |
| 密钥分发 | 困难,需要安全信道 | 简单,公钥可公开分发 |
| 加密速度 | 快(适合大数据量) | 慢(通常比对称密码慢100~1000倍) |
| 典型算法 | AES、DES、ChaCha20 | RSA、ECC、ElGamal |
| 主要用途 | 数据加密 | 密钥交换、数字签名、小数据加密 |
| 密钥长度 | 128/256位即可达到高安全 | 需要更长密钥(RSA 2048+位) |
公钥/私钥对的概念
非对称密码的核心在于密钥对(Key Pair):
- 公钥(Public Key):可以公开给任何人,用于加密消息或验证签名。
- 私钥(Private Key):必须严格保密,用于解密消息或生成签名。
两者在数学上相互关联,但从公钥推导私钥在计算上是不可行的——这正是非对称密码安全的根基。
非对称密码的三大应用
- 加密(Encryption):用公钥加密,用私钥解密。保证消息机密性,只有私钥持有者能读取内容。
- 数字签名(Digital Signature):用私钥签名,用公钥验证。保证消息完整性和身份认证。
- 密钥交换(Key Exchange):双方通过非对称算法协商出共享的对称密钥,再用于后续通信。
RSA算法
RSA算法由Ron Rivest、Adi Shamir和Leonard Adleman于1977年提出,是最广泛使用的非对称密码算法。RSA的安全性依赖于大整数分解的困难性。
RSA算法原理(一) 这篇博客详细介绍了RSA的数学原理。
数学原理:大数分解难题
RSA的安全性建立在一个简单但深刻的数学事实之上:将两个大素数相乘很容易,但将它们的乘积分解回原来的素数却极其困难。
例如,计算 $61 \times 53 = 3233$ 只需要一瞬间,但给定 $3233$,要找到它的素因子 $61$ 和 $53$ 则需要大量计算。当素数达到数百位十进制数时,即使用最强大的超级计算机,分解也需要数年时间。
密钥生成过程
RSA密钥对的生成包含以下步骤:
第一步:选择素数 $p$ 和 $q$
随机选择两个大素数 $p$ 和 $q$,通常要求两者长度相近。
$$p = 61, \quad q = 53$$
第二步:计算模数 $n$
$$n = p \times q = 61 \times 53 = 3233$$
$n$ 的二进制长度就是RSA的密钥长度。实际应用中,$n$ 至少为2048位。
第三步:计算欧拉函数 $\varphi(n)$
$$\varphi(n) = (p-1)(q-1) = 60 \times 52 = 3120$$
第四步:选择公钥指数 $e$
$e$ 的取值满足 $1 < e < \varphi(n)$ 且 $\gcd(e, \varphi(n)) = 1$。常用的值为 $e = 65537$(即 $2^{16}+1$),因为它只有两个二进制位为1,计算效率高。
$$e = 17$$
第五步:计算私钥指数 $d$
$d$ 是 $e$ 对 $\varphi(n)$ 的模反元素,即满足:
$$e \times d \equiv 1 \pmod{\varphi(n)}$$
$$17 \times d \equiv 1 \pmod{3120}$$
使用扩展欧几里得算法可求得 $d = 2753$。
最终,公钥为 $(n, e) = (3233, 17)$,私钥为 $(n, d) = (3233, 2753)$。
加密过程
发送方用公钥 $(n, e)$ 加密明文 $m$($m$ 必须是整数且 $0 \leq m < n$):
$$c = m^e \mod n$$
例如,加密 $m = 65$:
$$c = 65^{17} \mod 3233 = 2790$$
解密过程
接收方用私钥 $(n, d)$ 解密密文 $c$:
$$m = c^d \mod n$$
$$m = 2790^{2753} \mod 3233 = 65$$
明文被成功恢复。这背后的数学保证来自欧拉定理:因为 $e \times d \equiv 1 \pmod{\varphi(n)}$,所以 $m^{ed} \equiv m \pmod{n}$。
RSA的安全性分析
RSA的安全性取决于以下因素:
- 密钥长度:密钥越长,分解越困难。目前推荐最低2048位,敏感场景建议3072位或4096位。
- 素数质量:$p$ 和 $q$ 应为强素数(strong prime),且两者长度应相近,差值不宜太小。
- 随机性:密钥生成过程必须使用密码学安全的随机数生成器。
明文长度限制
RSA算法对明文长度的限制取决于密钥长度和填充方式。一般来说,RSA算法可以处理长度不超过密钥长度的明文。
例如,如果使用2048位的RSA密钥,则该算法可以处理的明文长度不能超过2048位。然而,实际上,RSA算法通常只用于加密较短的随机数或哈希值,而不是直接加密长明文。
对于加密短文本或密码等较短的明文,RSA可以很容易地处理。但对于加密大量数据,通常会使用对称加密算法来加密数据,然后使用RSA算法对对称密钥进行加密,以确保数据的安全性。
需要注意的是,对于RSA算法来说,密钥长度越长,安全性就越高。因此,在实际应用中,建议使用较长的密钥长度来确保足够的安全性。
不同填充方案下的最大明文长度(以2048位密钥为例):
| 填充方案 | 最大明文长度 | 说明 |
|---|---|---|
| PKCS#1 v1.5 | 245 字节 | 256 - 11(填充开销) |
| OAEP | 214 字节 | 256 - 42(SHA-1哈希开销) |
| OAEP-SHA256 | 190 字节 | 256 - 66(SHA-256哈希开销) |
| Raw(无填充) | 256 字节 | 不安全,不推荐使用 |
RSA Rust代码示例
#![allow(unused)]
fn main() {
// rand 和 ring::rand冲突了
extern crate rand as random;
#[test]
fn test_rsa() {
use rand_core::CryptoRngCore;
use rsa::{Pkcs1v15Encrypt, PublicKey, RsaPrivateKey, RsaPublicKey};
let mut sw = Stopwatch::new();
let mut rng = random::thread_rng();
let bits = 2048;
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let pub_key = RsaPublicKey::from(&priv_key);
println!("elapsed 0:{:?}", sw.elapsed());
sw.restart();
// Encrypt
let plaintext = String::from(
"绝密级国家秘密是最重要的国家秘密,泄露会使国家安全和利益遭受特别严重的损害;",
);
let data = plaintext.as_bytes();
println!("data len is {}", data.len());
let enc_data = pub_key
.encrypt(&mut rng, Pkcs1v15Encrypt, &data[..])
.expect("failed to encrypt");
println!("elapsed 1:{:?}", sw.elapsed());
assert_ne!(&data[..], &enc_data[..]);
println!("{:?}", base64::encode(&enc_data[..]));
sw.restart();
// Decrypt
let dec_data = priv_key
.decrypt(Pkcs1v15Encrypt, &enc_data)
.expect("failed to decrypt");
println!("elapsed 2:{:?}", sw.elapsed());
assert_eq!(&data[..], &dec_data[..]);
println!("{}",String::from_utf8_lossy(&dec_data[..]));
}
}
上述代码演示了RSA的基本加密解密流程。RsaPrivateKey::new 生成2048位密钥对,pub_key.encrypt 使用PKCS#1 v1.5填充方案加密,priv_key.decrypt 则进行解密。注意明文长度不能超过密钥长度减去填充开销。
RSA-OAEP更安全的填充方案
PKCS#1 v1.5存在一些已知攻击(如Bleichenbacher攻击),推荐使用OAEP填充:
#![allow(unused)]
fn main() {
use rsa::{Oaep, RsaPublicKey, RsaPrivateKey};
use sha2::Sha256;
#[test]
fn test_rsa_oaep() {
use rand_core::CryptoRngCore;
use rsa::PublicKey;
let mut rng = rand::thread_rng();
let priv_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
let pub_key = RsaPublicKey::from(&priv_key);
let data = b"secret message";
// 使用OAEP + SHA-256填充
let padding = Oaep::new::<sha2::Sha256>();
let ciphertext = pub_key.encrypt(&mut rng, padding, data).unwrap();
let plaintext = priv_key.decrypt(Oaep::new::<sha2::Sha256>(), &ciphertext).unwrap();
assert_eq!(data, &plaintext[..]);
}
}
RSA数字签名
RSA不仅可以用于加密,还可以用于数字签名。签名时使用私钥,验证时使用公钥:
#![allow(unused)]
fn main() {
use rsa::pkcs8::EncodePrivateKey;
use rsa::{RsaPrivateKey, RsaPublicKey, Pss, pkcs8::DecodePrivateKey};
use sha2::{Sha256, Digest};
#[test]
fn test_rsa_signature() {
use rand_core::CryptoRngCore;
let mut rng = rand::thread_rng();
let priv_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
let pub_key = RsaPublicKey::from(&priv_key);
let message = b"这是一条需要签名的消息";
// 对消息进行SHA-256哈希
let mut hasher = Sha256::new();
hasher.update(message);
let hash = hasher.finalize();
// 使用私钥签名(PSS填充)
let signature = priv_key.sign(Pss::new::<Sha256>(), &hash).unwrap();
// 使用公钥验证签名
let verified = pub_key.verify(Pss::new::<Sha256>(), &hash, &signature);
assert!(verified.is_ok());
}
}
常见攻击与防御
选择密文攻击(Chosen-Ciphertext Attack)
攻击者构造特殊的密文让目标解密,通过分析解密结果逐步推导私钥。防御方法:使用OAEP填充而非PKCS#1 v1.5。
计时攻击(Timing Attack)
攻击者通过测量解密操作的耗时来推断私钥信息。防御方法:使用恒定时间算法(constant-time implementation),ring 和 rsa crate 内部已做了此类防护。
小公钥指数攻击
当 $e$ 很小且明文也很小时,$m^e$ 可能小于 $n$,此时 $c = m^e$ 而非 $m^e \mod n$,直接开 $e$ 次方根即可得到明文。防御方法:确保使用适当的填充方案。
Diffie-Hellman密钥交换
Diffie-Hellman(DH)密钥交换由Whitfield Diffie和Martin Hellman于1976年提出,是第一个实用的公钥密码方案。它不直接用于加密或解密,而是让双方在不安全信道上协商出一个共享密钥。
原理:离散对数问题
DH的安全性基于离散对数问题(Discrete Logarithm Problem, DLP):给定 $g$、$p$ 和 $g^x \mod p$,很难求出 $x$。
数学公式
假设Alice和Bob要协商共享密钥:
公共参数:大素数 $p$ 和生成元 $g$
Alice侧:
- 随机选择私钥 $a$
- 计算公钥 $A = g^a \mod p$
- 将 $A$ 发送给Bob
Bob侧:
- 随机选择私钥 $b$
- 计算公钥 $B = g^b \mod p$
- 将 $B$ 发送给Alice
计算共享密钥:
- Alice计算:$s = B^a \mod p = g^{ab} \mod p$
- Bob计算:$s = A^b \mod p = g^{ab} \mod p$
双方得到相同的共享密钥 $s$,而窃听者只能看到 $g$、$p$、$A$、$B$,无法计算出 $s$。
中间人攻击风险
DH协议本身不验证通信双方的身份,因此容易遭受中间人攻击(Man-in-the-Middle, MITM):
- 攻击者Eve截获Alice发送的 $A$,替换为自己的 $E$
- Eve截获Bob发送的 $B$,替换为自己的 $E’$
- Eve分别与Alice和Bob建立独立的共享密钥
- Eve可以解密、修改、重新加密所有通信内容
防御方法:对DH交换过程进行身份认证,通常结合数字证书(如TLS中的DH握手)。
Rust代码示例
#![allow(unused)]
fn main() {
use rand::RngCore;
use num_bigint::{BigUint, RandBigInt};
/// Diffie-Hellman密钥交换示例
#[test]
fn test_diffie_hellman() {
// 公共参数(实际应用中应使用标准化的参数组)
let p = BigUint::parse_bytes(
b"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff",
16,
).unwrap();
let g = BigUint::from(2u32);
let mut rng = rand::thread_rng();
// Alice生成密钥对
let a = rng.gen_biguint_below(&p);
let A = g.modpow(&a, &p);
// Bob生成密钥对
let b = rng.gen_biguint_below(&p);
let B = g.modpow(&b, &p);
// 双方计算共享密钥
let alice_shared = B.modpow(&a, &p);
let bob_shared = A.modpow(&b, &p);
assert_eq!(alice_shared, bob_shared, "共享密钥应一致");
println!("共享密钥协商成功!");
}
}
注意:上述示例使用
num-bigint进行教学演示。生产环境应使用ring或p256等经过审计的密码学库。
ECC椭圆曲线密码学
椭圆曲线密码学是目前最主流的非对称密码技术,相较于RSA,ECC的主要优势是在使用更小的密钥的同时,提供更快的性能和更高等级的安全。
椭圆曲线数学基础
椭圆曲线是满足以下方程的点集:
$$y^2 = x^3 + ax + b$$
其中 $a$ 和 $b$ 是系数,满足 $4a^3 + 27b^2 \neq 0$(保证曲线无奇点)。
在密码学中,我们关注的是椭圆曲线上的有理点集合,并定义两种核心运算:
点加法(Point Addition)
给定曲线上的两点 $P$ 和 $Q$,过这两点作直线,与曲线的第三个交点关于x轴的对称点即为 $P + Q$。
点乘(Scalar Multiplication)
给定点 $P$ 和整数 $k$,点乘定义为:
$$kP = \underbrace{P + P + \cdots + P}_{k \text{ 次}}$$
点乘可以通过“双倍-加“(Double-and-Add)算法高效计算,这是ECC效率的关键。
ECDLP:椭圆曲线离散对数问题
ECC的安全性基于椭圆曲线离散对数问题(Elliptic Curve Discrete Logarithm Problem, ECDLP):
给定椭圆曲线上的点 $G$ 和 $kG$,求出整数 $k$ 在计算上是不可行的。
这与DH中的离散对数问题类似,但在椭圆曲线上,即使使用更小的参数也能达到同等甚至更高的安全强度。
ECC与RSA对比
| 安全等级(比特) | RSA密钥长度 | ECC密钥长度 | ECC/RSA倍率 |
|---|---|---|---|
| 80 | 1024 | 160 | 6.4x |
| 112 | 2048 | 224 | 9.1x |
| 128 | 3072 | 256 | 12.0x |
| 192 | 7680 | 384 | 20.0x |
| 256 | 15360 | 521 | 29.5x |
可以看到,256位安全级别下,ECC仅需521位密钥,而RSA需要15360位——ECC的密钥长度不到RSA的1/29。
ECDH密钥交换
ECDH(Elliptic Curve Diffie-Hellman)是DH协议在椭圆曲线上的实现,原理相同但使用椭圆曲线点运算替代模幂运算:
$$\text{Alice: } A = aG, \quad s = bA = abG$$ $$\text{Bob: } B = bG, \quad s = aB = abG$$
#![allow(unused)]
fn main() {
use p256::{EncodedPoint, PublicKey, SecretKey};
use p256::ecdh::{EphemeralSecret, SharedSecret};
#[test]
fn test_ecdh() {
use rand_core::OsRng;
// Alice生成临时密钥对
let alice_secret = EphemeralSecret::random(OsRng);
let alice_public = alice_secret.public_key();
// Bob生成临时密钥对
let bob_secret = EphemeralSecret::random(OsRng);
let bob_public = bob_secret.public_key();
// Alice计算共享密钥
let alice_shared = SharedSecret::from(alice_secret, &bob_public);
// Bob计算共享密钥
let bob_shared = SharedSecret::from(bob_secret, &alice_public);
// 双方共享密钥一致
assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
println!("ECDH共享密钥协商成功!");
}
}
ECDSA数字签名
ECDSA(Elliptic Curve Digital Signature Algorithm)是基于椭圆曲线的数字签名算法,广泛用于TLS、SSH、区块链等领域。
#![allow(unused)]
fn main() {
use p256::{ecdsa::{Signature, SigningKey, VerifyingKey, signature::{Signer, Verifier}}};
#[test]
fn test_ecdsa() {
use rand_core::OsRng;
// 生成签名密钥对
let signing_key = SigningKey::random(OsRng);
let verifying_key = signing_key.verifying_key();
let message = b"这是一条需要ECDSA签名的消息";
// 签名
let signature: Signature = signing_key.sign(message);
println!("签名: {}", hex::encode(signature.to_bytes()));
// 验证签名
let verified = verifying_key.verify(message, &signature);
assert!(verified.is_ok(), "签名验证应成功");
// 篡改消息后验证应失败
let tampered = b"这是一条被篡改的消息";
let failed = verifying_key.verify(tampered, &signature);
assert!(failed.is_err(), "篡改后签名验证应失败");
}
}
SM2国密椭圆曲线
SM2是中国国家密码管理局发布的椭圆曲线公钥密码算法,基于256位素数域椭圆曲线,安全性等同于约128位对称密钥强度。
#![allow(unused)]
fn main() {
use libsm::sm2::ecc::Point;
use libsm::sm2::encrypt::{DecryptCtx, EncryptCtx};
use libsm::sm2::signature::{SigCtx, Signature};
use libsm::sm3::hash::Sm3Hash;
use libsm::sm4::Cipher;
use libsm::sm4::Mode;
use num::BigUint;
#[test]
fn sm2() {
let poem2 = String::from("南国烽烟正十年,此头须向国门悬。后死诸君多努力,捷报飞来当纸钱。");
let msg = poem2.as_bytes();
let ctx = SigCtx::new();
let base64_pk = base64::decode("A8AG7dZ1AiuRHJ4Wumkt0ecGaVLGdgZXNcPO5YbvlUGl").unwrap();
let pk = ctx.load_pubkey(&base64_pk).unwrap();
//SM2加密、解密
let klen = msg.len();
let encrypt_ctx = EncryptCtx::new(klen, pk);
let cipher = encrypt_ctx.encrypt(msg).unwrap();
let decrypt_ctx = DecryptCtx::new(klen, sk);
let plain = decrypt_ctx.decrypt(&cipher).unwrap();
assert_eq!(msg, plain);
}
}
SM2算法包含三个子算法:SM2加密解密算法、SM2数字签名算法和SM2密钥交换协议。SM2曲线的方程为:
$$y^2 = x^3 + ax + b \pmod p$$
其中 $p$ 为256位素数,$a = p - 3$,$b$ 为特定常量。SM2的密钥长度为256位,安全强度与RSA-3072相当,但密钥短得多。
其他非对称算法简介
ElGamal加密
ElGamal加密算法由 Taher ElGamal 于1985年提出,安全性基于离散对数问题。它是一种概率加密方案,每次加密同一明文会产生不同密文。
加密过程:
- 选择大素数 $p$ 和生成元 $g$
- 接收方私钥 $x$,公钥 $y = g^x \mod p$
- 加密:选择随机数 $k$,计算 $c_1 = g^k \mod p$,$c_2 = m \cdot y^k \mod p$
- 密文为 $(c_1, c_2)$
解密过程:$m = c_2 \cdot (c_1^x)^{-1} \mod p$
ElGamal密文长度是明文的两倍,且加密速度较慢,因此主要用于学术研究和特定场景。
DSA数字签名算法
DSA(Digital Signature Algorithm)是美国联邦数字签名标准(FIPS 186),基于离散对数问题,专门用于数字签名。
与RSA签名相比,DSA的设计专门针对签名场景优化,但签名速度较慢。目前在实际应用中,DSA正逐渐被ECDSA和EdDSA取代。
非对称密码的性能问题:混合加密方案
非对称密码算法的速度远慢于对称密码(通常慢100到1000倍),且对明文长度有严格限制。因此,在实际应用中,普遍采用混合加密方案(Hybrid Encryption),也称数字信封(Digital Envelope)。
数字信封的工作流程
发送方:
1. 生成随机对称密钥 K(如AES-256密钥)
2. 用对称密钥 K 加密明文数据 → 密文数据
3. 用接收方公钥加密对称密钥 K → 加密后的密钥
4. 发送:[加密后的密钥] + [密文数据]
接收方:
1. 用自己的私钥解密 → 对称密钥 K
2. 用对称密钥 K 解密密文数据 → 明文数据
混合加密Rust实现
#![allow(unused)]
fn main() {
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
use rsa::{RsaPrivateKey, RsaPublicKey, Pkcs1v15Encrypt, PublicKey};
use rand_core::CryptoRngCore;
/// 数字信封:混合加密
struct DigitalEnvelope {
encrypted_key: Vec<u8>, // RSA加密的AES密钥
ciphertext: Vec<u8>, // AES-GCM加密的数据
nonce: Vec<u8>, // AES-GCM的nonce
}
fn seal(
plaintext: &[u8],
pub_key: &RsaPublicKey,
rng: &mut impl CryptoRngCore,
) -> Result<DigitalEnvelope, Box<dyn std::error::Error>> {
// 1. 生成随机AES密钥
let aes_key = Aes256Gcm::generate_key(rng);
let cipher = Aes256Gcm::new(&aes_key);
// 2. 生成随机nonce
let nonce =Nonce::from_slice(&[0u8; 12]); // 实际应用中应使用随机nonce
// 3. 用AES-GCM加密数据
let ciphertext = cipher.encrypt(nonce, plaintext)?;
// 4. 用RSA公钥加密AES密钥
let encrypted_key = pub_key.encrypt(rng, Pkcs1v15Encrypt, aes_key.as_slice())?;
Ok(DigitalEnvelope {
encrypted_key,
ciphertext,
nonce: nonce.to_vec(),
})
}
fn unseal(
envelope: &DigitalEnvelope,
priv_key: &RsaPrivateKey,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
// 1. 用RSA私钥解密AES密钥
let aes_key_bytes = priv_key.decrypt(Pkcs1v15Encrypt, &envelope.encrypted_key)?;
let aes_key = Key::<Aes256Gcm>::from_slice(&aes_key_bytes);
let cipher = Aes256Gcm::new(aes_key);
// 2. 用AES-GCM解密数据
let nonce = Nonce::from_slice(&envelope.nonce);
let plaintext = cipher.decrypt(nonce, envelope.ciphertext.as_ref())?;
Ok(plaintext)
}
#[test]
fn test_digital_envelope() {
let mut rng = rand::thread_rng();
let priv_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
let pub_key = RsaPublicKey::from(&priv_key);
let long_message = b"这是一段很长的机密消息,混合加密方案可以高效地处理任意长度的数据。".repeat(100);
let long_message = long_message.as_slice();
// 加密
let envelope = seal(long_message, &pub_key, &mut rng).unwrap();
// 解密
let decrypted = unseal(&envelope, &priv_key).unwrap();
assert_eq!(long_message, decrypted.as_slice());
println!("数字信封加解密成功!原始数据长度:{}字节", long_message.len());
}
}
这种方案兼具了对称密码的高效性和非对称密码的密钥管理便利性,是TLS/SSL、PGP、S/MIME等协议的核心技术。
Rust非对称密码库推荐
| 库名 | 用途 | 特点 |
|---|---|---|
rsa | RSA加密/解密/签名 | 纯Rust实现,API友好,支持PKCS#1和OAEP |
ring | 通用密码学库 | Google出品,经过严格审计,支持RSA/ECC/HMAC等 |
p256 | NIST P-256椭圆曲线 | 纯Rust实现,支持ECDH/ECDSA |
ed25519-dalek | Ed25519签名 | 纯Rust实现,高性能,广泛用于SSH/TLS |
x25519-dalek | X25519密钥交换 | Diffie-Hellman的椭圆曲线版本 |
libsm | 国密SM2/SM3/SM4 | 中国国密算法的Rust实现 |
Cargo.toml 依赖示例:
[dependencies]
rsa = { version = "0.9", features = ["sha2"] }
rand = "0.8"
sha2 = "0.10"
p256 = { version = "0.13", features = ["ecdh", "ecdsa"] }
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
ring = "0.17"
hex = "0.4"
总结
| 特性 | RSA | ECC (P-256) | SM2 |
|---|---|---|---|
| 数学基础 | 大数分解问题 | 椭圆曲线离散对数问题 | 椭圆曲线离散对数问题 |
| 推荐密钥长度 | 2048~4096 位 | 256 位 | 256 位 |
| 加密速度 | 慢 | 较快 | 较快 |
| 签名速度 | 较慢 | 快 | 快 |
| 密文长度 | 与密钥长度相同 | 与密钥长度相同 | 与密钥长度相同 |
| 安全等级(128位) | 3072 位 | 256 位 | 256 位 |
| 国际标准 | PKCS#1, RFC 8017 | NIST FIPS 186-4 | GB/T 32918 |
| 应用场景 | TLS、代码签名、证书 | TLS、区块链、SSH | 国密体系、政务系统 |
| Rust crate | rsa, ring | p256, ring | libsm |
练习题
练习1:RSA密钥生成与加解密
使用 rsa crate 生成4096位RSA密钥对,加密一段中文消息,然后解密并验证。比较2048位和4096位密钥的生成时间和加解密时间。
练习2:ECDH密钥交换
使用 p256 crate 实现ECDH密钥交换。模拟Alice和Bob双方各自生成密钥对,交换公钥,计算共享密钥,并验证一致性。
练习3:ECDSA签名与验证
使用 ed25519-dalek crate 对一段消息进行签名,然后验证签名。尝试篡改消息内容,观察验证结果的变化。
练习4:数字信封实现
基于RSA和AES-GCM实现完整的数字信封方案,要求:
- 支持任意长度的明文
- 使用OAEP填充方案
- 使用随机nonce
- 编写完整的加密和解密函数
练习5:算法对比实验
编写基准测试(benchmark),比较以下操作的性能:
- RSA-2048 vs P-256 的密钥生成时间
- RSA-2048 vs P-256 的签名时间
- RSA-2048 vs P-256 的验证时间
使用 criterion crate 进行精确的性能测量,输出对比报告。
练习6:SM2签名验证
使用 libsm crate 实现SM2数字签名和验证流程。生成SM2密钥对,对消息签名,然后验证签名的有效性。