Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第三十 非对称密码算法

非对称密码算法(Asymmetric Cryptography),又称公钥密码学(Public-Key Cryptography),是现代信息安全的基石。与对称密码使用同一把密钥进行加密和解密不同,非对称密码使用一对数学上关联的密钥——公钥和私钥。这一突破性的概念由Whitfield Diffie和Martin Hellman于1976年提出,彻底改变了密码学的面貌。

非对称密码概述

对称密码 vs 非对称密码

在对称密码体系中,加密和解密使用同一把密钥,密钥分发是其最大的难题——如何在不安全的信道上安全地传递密钥?非对称密码正是为解决这一问题而诞生的。

对比维度对称密码非对称密码
密钥数量一把密钥(加密和解密共用)一对密钥(公钥 + 私钥)
密钥分发困难,需要安全信道简单,公钥可公开分发
加密速度快(适合大数据量)慢(通常比对称密码慢100~1000倍)
典型算法AES、DES、ChaCha20RSA、ECC、ElGamal
主要用途数据加密密钥交换、数字签名、小数据加密
密钥长度128/256位即可达到高安全需要更长密钥(RSA 2048+位)

公钥/私钥对的概念

非对称密码的核心在于密钥对(Key Pair):

  • 公钥(Public Key):可以公开给任何人,用于加密消息或验证签名。
  • 私钥(Private Key):必须严格保密,用于解密消息或生成签名。

两者在数学上相互关联,但从公钥推导私钥在计算上是不可行的——这正是非对称密码安全的根基。

非对称密码的三大应用

  1. 加密(Encryption):用公钥加密,用私钥解密。保证消息机密性,只有私钥持有者能读取内容。
  2. 数字签名(Digital Signature):用私钥签名,用公钥验证。保证消息完整性和身份认证。
  3. 密钥交换(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.5245 字节256 - 11(填充开销)
OAEP214 字节256 - 42(SHA-1哈希开销)
OAEP-SHA256190 字节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),ringrsa 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侧

  1. 随机选择私钥 $a$
  2. 计算公钥 $A = g^a \mod p$
  3. 将 $A$ 发送给Bob

Bob侧

  1. 随机选择私钥 $b$
  2. 计算公钥 $B = g^b \mod p$
  3. 将 $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):

  1. 攻击者Eve截获Alice发送的 $A$,替换为自己的 $E$
  2. Eve截获Bob发送的 $B$,替换为自己的 $E’$
  3. Eve分别与Alice和Bob建立独立的共享密钥
  4. 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 进行教学演示。生产环境应使用 ringp256 等经过审计的密码学库。

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倍率
8010241606.4x
11220482249.1x
128307225612.0x
192768038420.0x
2561536052129.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年提出,安全性基于离散对数问题。它是一种概率加密方案,每次加密同一明文会产生不同密文。

加密过程

  1. 选择大素数 $p$ 和生成元 $g$
  2. 接收方私钥 $x$,公钥 $y = g^x \mod p$
  3. 加密:选择随机数 $k$,计算 $c_1 = g^k \mod p$,$c_2 = m \cdot y^k \mod p$
  4. 密文为 $(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非对称密码库推荐

库名用途特点
rsaRSA加密/解密/签名纯Rust实现,API友好,支持PKCS#1和OAEP
ring通用密码学库Google出品,经过严格审计,支持RSA/ECC/HMAC等
p256NIST P-256椭圆曲线纯Rust实现,支持ECDH/ECDSA
ed25519-dalekEd25519签名纯Rust实现,高性能,广泛用于SSH/TLS
x25519-dalekX25519密钥交换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"

总结

特性RSAECC (P-256)SM2
数学基础大数分解问题椭圆曲线离散对数问题椭圆曲线离散对数问题
推荐密钥长度2048~4096 位256 位256 位
加密速度较快较快
签名速度较慢
密文长度与密钥长度相同与密钥长度相同与密钥长度相同
安全等级(128位)3072 位256 位256 位
国际标准PKCS#1, RFC 8017NIST FIPS 186-4GB/T 32918
应用场景TLS、代码签名、证书TLS、区块链、SSH国密体系、政务系统
Rust cratersa, ringp256, ringlibsm

练习题

练习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密钥对,对消息签名,然后验证签名的有效性。