当你用 SSH 连接远程服务器时,当你用 git push 推送代码时,当你在浏览器地址栏看到那把小锁图标时——非对称加密正在默默保护你的身份和数据。它的神奇之处在于:即使所有人都知道你的加密方式,没有私钥的人依然无法解密。
在 对称加密 中理解了 AES 与 ChaCha20。对称加密很快,但有一个绕不过去的问题:加密和解密用同一个密钥,双方怎么安全地把密钥传给对方?非对称加密就是为解决这个难题而生的。
一、非对称加密基础
1.1 公钥与私钥
非对称加密使用一对密钥:公钥公开,私钥保密:
| 操作 | 使用的密钥 | 目的 |
|---|---|---|
| 加密 | 接收方公钥 | 只有私钥持有者能解密 |
| 解密 | 接收方私钥 | 恢复明文 |
| 签名 | 发送方私钥 | 证明身份 |
| 验证 | 发送方公钥 | 确认签名 |
1.2 数学单向函数
非对称加密的根基是数学单向函数——正向计算容易,逆向求解极难:
- RSA:大数乘法容易(),大数分解困难(已知 求 和 )
- ECC:标量乘法容易(),已知 和 求 极难(ECDLP)
- DH:模幂运算容易(),求离散对数困难
1.3 完整生命周期
1.4 为什么非对称加密这么慢
# 对比 RSA 和 AES 的加密速度# AES-256-GCM 加密 1MB 数据openssl speed -evp aes-256-gcm -bytes 1048576# → ~3.5 GB/s
# RSA-2048 签名速度openssl speed rsa2048# → ~1000 signs/s (每秒约 1000 次签名)
# 性能差距:AES 比 RSA 快约 3500 倍# 这就是为什么 TLS 用 RSA/ECDH 交换密钥,用 AES 加密数据| 操作类型 | AES-128 | RSA-2048 | ECC P-256 | 速度比 |
|---|---|---|---|---|
| 加密 1MB 数据 | ~1ms | ~200,000ms | 不适用 | AES 快 200,000x |
| 密钥交换 | 不适用 | ~2ms | ~2ms | — |
| 签名 | 不适用 | ~2ms | ~1ms | — |
非对称加密从不直接加密大量数据。实际系统使用混合加密:非对称加密协商对称密钥,对称密钥加密数据。这就是 TLS、PGP、SSH 的工作方式。
1.5 两大算法家族
| 维度 | RSA | ECC |
|---|---|---|
| 数学基础 | 大数分解 | 椭圆曲线离散对数 |
| 密钥长度 | 2048-4096 位 | 256-521 位 |
| 性能 | 慢 | 快(10-100x) |
| 密钥大小 | 大 | 小 |
| 时代 | 1977 年 | 1985 年 |
二、RSA 算法
2.1 RSA 密钥生成:逐步推导
第一步:选择两个大素数 p, q p = 61, q = 53(教学用小素数,实际使用 1024 位素数)
第二步:计算模数 n n = p × q = 61 × 53 = 3233
第三步:计算欧拉函数 φ(n) φ(n) = (p-1)(q-1) = 60 × 52 = 3120
第四步:选择公钥指数 e e = 17,满足 gcd(e, φ(n)) = 1 通常选择 65537(0x10001)
第五步:计算私钥指数 d d = e⁻¹ mod φ(n) = 17⁻¹ mod 3120 = 2753 验证:e × d mod φ(n) = 17 × 2753 mod 3120 = 1
公钥 = (n=3233, e=17)私钥 = (n=3233, d=2753)2.2 RSA 加密与解密:小数示例
用上面的密钥加密消息 m = 65(ASCII ‘A’):
加密:c = m^e mod n = 65^17 mod 3233 = 2790解密:m = c^d mod n = 2790^2753 mod 3233 = 65
签名:s = m^d mod n验证:m' = s^e mod n → m' == m实际 RSA 中消息必须先经过填充(padding)处理。上例省略了填充步骤仅用于教学演示。
2.3 RSA 的安全基础
RSA 的安全性基于大整数分解问题:
| 攻击思路 | 说明 | 难度 |
|---|---|---|
| 分解 n → 得到 p, q | 直接分解模数 | 极难(2048 位约 2^112 次运算) |
| 已知 φ(n) → 求 d | 等价于分解 n | 同上 |
| 已知 d → 分解 n | 可从 d 反推 p, q | 存在高效算法 |
目前最大 RSA 分解记录是 RSA-250(829 位),于 2020 年完成,耗费约 2700 CPU 核心年。2048 位 RSA 当前安全,但量子计算中的 Shor 算法可在多项式时间内分解大数——这是后量子密码学的研究动机。
2.4 RSA 填充方案
| 填充方案 | 安全性 | 说明 | 适用场景 |
|---|---|---|---|
| PKCS#1 v1.5 | 不安全 | 易受 Bleichenbacher 攻击 | 仅兼容旧系统 |
| OAEP | 安全 | 最优非对称加密填充 | 加密 |
| PSS | 安全 | 概率签名方案 | 签名 |
永远不要使用 PKCS#1 v1.5 填充——Bleichenbacher 攻击可以通过 Padding Oracle 恢复明文。加密使用 OAEP,签名使用 PSS。
OAEP 通过引入随机性,使相同明文每次加密产生不同密文,杜绝确定性加密带来的信息泄露。
2.5 RSA 密钥长度选择
| 安全等级 | RSA 最小长度 | NIST 建议有效期 | 备注 |
|---|---|---|---|
| 80 位 | 1024 位 | 已不安全 | 2010 年后不再推荐 |
| 112 位 | 2048 位 | 到 2030 年 | 当前最低要求 |
| 128 位 | 3072 位 | 2030+ | 新系统推荐 |
| 192 位 | 7680 位 | 长期 | 高安全场景 |
| 256 位 | 15360 位 | 最高安全 | 极端场景 |
新系统建议直接使用 RSA-3072 或转向 ECC。Let’s Encrypt 从 2020 年起默认签发 ECC 证书。
2.6 RSA 代码示例
# Python: RSA 完整操作——密钥生成、加密、签名from cryptography.hazmat.primitives.asymmetric import rsa, paddingfrom cryptography.hazmat.primitives import hashes, serialization
# 1. 生成 RSA 密钥对(推荐 3072 位)private_key = rsa.generate_private_key(public_exponent=65537, key_size=3072)public_key = private_key.public_key()
# 2. 加密(OAEP 填充)message = b"Hello, RSA!"ciphertext = public_key.encrypt( message, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
# 3. 解密plaintext = private_key.decrypt( ciphertext, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))assert plaintext == message
# 4. 签名(PSS 填充)signature = private_key.sign( message, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())
# 5. 验证签名public_key.verify( signature, message, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())2.7 RSA 性能与混合加密
| 操作 | RSA-2048 | RSA-3072 | RSA-4096 |
|---|---|---|---|
| 密钥生成 | ~1s | ~5s | ~10s |
| 加密 | ~0.1ms | ~0.3ms | ~0.5ms |
| 解密 | ~2ms | ~7ms | ~10ms |
| 签名 | ~2ms | ~7ms | ~10ms |
| 验证 | ~0.1ms | ~0.3ms | ~0.5ms |
RSA 不适合加密大量数据——通常用于加密对称密钥(混合加密):
三、椭圆曲线密码学(ECC)
3.1 为什么 ECC 密钥更短
ECC 的底层数学问题难度随密钥长度指数级增长,而 RSA 只能亚指数级增长:
达到 128 位安全:RSA 需要 3072 位,ECC 只需 256 位(12:1)达到 256 位安全:RSA 需要 15360 位,ECC 只需 521 位(29:1)密钥越长,ECC 的优势越明显。
3.2 椭圆曲线数学基础
椭圆曲线方程:
- 点加法:曲线上 P + Q = R,几何意义是连接 P、Q 的直线与曲线的第三个交点关于 x 轴的对称点
- 标量乘法:(k 次),使用倍加算法只需 次运算
- ECDLP:已知 ,求 极难——这就是 ECC 的安全基础
标量乘法示例(倍加法):计算 13 × P:13 = 1101(二进制) P → 2P → 4P → 8P(逐次倍加) 13P = 8P + 4P + P(按二进制位累加) 只需 log₂(13) ≈ 4 次运算,而非 13 次实际运算在有限域(素数域 )上进行,点的坐标是整数对 ,其中 。
3.3 常用曲线对比
| 曲线 | 密钥长度 | 安全等级 | 设计者 | 特点 | 典型用途 |
|---|---|---|---|---|---|
| P-256 | 256 位 | 128 位 | NIST | 最广泛支持 | TLS 证书、代码签名 |
| P-384 | 384 位 | 192 位 | NIST | 更高安全等级 | 政府级应用 |
| Curve25519 | 256 位 | 128 位 | Bernstein | 高性能、安全实现 | 密钥交换(ECDH) |
| Ed25519 | 256 位 | 128 位 | Bernstein | 确定性签名 | 数字签名(SSH、Git) |
| Ed448 | 448 位 | 224 位 | Hamburg | 更高安全等级 | 高安全签名 |
Curve25519/Ed25519 是 Bernstein 设计的”安全曲线”,避免了 NIST 曲线的实现陷阱(如不确定的随机数种子来源)。新项目推荐优先使用。
3.4 ECDH 密钥交换
# Python: ECDH 密钥交换(Curve25519)from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKeyfrom cryptography.hazmat.primitives.kdf.hkdf import HKDFfrom cryptography.hazmat.primitives import hashes
alice_private = X25519PrivateKey.generate()bob_private = X25519PrivateKey.generate()
# 交换公钥,计算共享密钥alice_shared = alice_private.exchange(bob_private.public_key())bob_shared = bob_private.exchange(alice_private.public_key())assert alice_shared == bob_shared
# 从共享密钥派生对称密钥(不要直接使用原始共享密钥!)derived_key = HKDF( algorithm=hashes.SHA256(), length=32, salt=None, info=b"handshake data",).derive(alice_shared)3.5 ECDSA 数字签名
签名过程:
1. h = Hash(message)2. 生成随机数 k(关键!k 泄露 = 私钥泄露)3. (x₁, y₁) = k × G4. r = x₁ mod n5. s = k⁻¹ × (h + r × d) mod n(d 为私钥)6. 签名 = (r, s)验证过程:
1. h = Hash(message)2. w = s⁻¹ mod n3. (x₁, y₁) = (h × w) × G + (r × w) × Q(Q 为公钥)4. r ≡ x₁ mod n → 签名有效# Python: ECDSA 签名与验证from cryptography.hazmat.primitives.asymmetric import ecfrom cryptography.hazmat.primitives import hashes
message = b"Important document"private_key = ec.generate_private_key(ec.SECP256R1())signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
public_key = private_key.public_key()public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))3.6 EdDSA 签名(Ed25519)
EdDSA 的核心改进是确定性签名——不需要随机数:
# Python: Ed25519 签名(推荐用于新系统)from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
private_key = Ed25519PrivateKey.generate()signature = private_key.sign(b"Important document")private_key.public_key().verify(signature, b"Important document")| 维度 | ECDSA | EdDSA |
|---|---|---|
| 曲线 | P-256 等 | Ed25519 |
| 随机数 | 需要(k 泄露=私钥泄露) | 确定性(无需随机数) |
| 性能 | 中 | 快 |
| 签名长度 | 64 字节 | 64 字节 |
| 推荐 | 兼容性优先 | 新系统优先 |
2010 年 Sony PS3 被破解,就是因为签名时使用了固定的 k 值,攻击者从两个签名中反推出私钥。EdDSA 通过确定性派生 k 值彻底消除了这个风险。
四、密钥交换协议
4.1 Diffie-Hellman 密钥交换
DH 是非对称密码学最优雅的发明——让两个从未谋面的人在不安全信道上协商出共享密钥:
DH 的数学保证:已知 和 计算 是 CDH 问题,目前没有高效算法。
4.2 为什么需要临时密钥(Ephemeral)
静态 DH 密钥交换的致命问题:私钥一旦泄露,所有历史通信都可被解密。前向安全性(Forward Secrecy)要解决的就是这个问题。
| 模式 | 缩写 | 前向安全 | 说明 |
|---|---|---|---|
| 静态 DH | DH | 密钥固定,泄露后历史通信可解密 | |
| 临时 DH | DHE | 每次生成新密钥对,泄露不影响历史 | |
| 静态 ECDH | ECDH | 椭圆曲线版本,同上 | |
| 临时 ECDH | ECDHE | TLS 1.3 强制使用 |
前向安全意味着”即使今天的密钥泄露,昨天的通信仍然安全”。TLS 1.3 强制使用 ECDHE,不再支持静态密钥交换。
4.3 密钥交换在 TLS 中的应用
TLS 1.3 握手精简为 1-RTT,核心就是 ECDHE:
| 密钥交换组 | 曲线 | 安全等级 | 推荐度 |
|---|---|---|---|
| x25519 | Curve25519 | 128 位 | 首选 |
| secp256r1 | P-256 | 128 位 | 兼容 |
| secp384r1 | P-384 | 192 位 | 高安全 |
| ffdhe2048 | DH 2048 位 | 112 位 | 不推荐 |
五、RSA vs ECC 全面对比
5.1 性能基准
| 操作 | RSA-2048 | RSA-3072 | RSA-4096 | P-256 | Curve25519 |
|---|---|---|---|---|---|
| 密钥生成 | 1000ms | 5000ms | 10000ms | 1ms | 0.5ms |
| 加密/验证 | 0.1ms | 0.3ms | 0.5ms | — | — |
| 解密/签名 | 2ms | 7ms | 10ms | 1ms | 0.5ms |
| 密钥交换 | — | — | — | 2ms | 0.3ms |
5.2 安全等级与密钥长度映射
| 安全等级 | 对称密钥 | RSA | ECC | DH | 有效期 |
|---|---|---|---|---|---|
| 80 位 | 80 | 1024 | 160 | 1024 | 已不安全 |
| 112 位 | 112 | 2048 | 224 | 2048 | 到 2030 |
| 128 位 | 128 | 3072 | 256 | 3072 | 2030+ |
| 192 位 | 192 | 7680 | 384 | 7680 | 长期 |
| 256 位 | 256 | 15360 | 521 | 15360 | 最高安全 |
5.3 传输开销对比
| 操作 | RSA-2048 | RSA-4096 | P-256 | Curve25519 |
|---|---|---|---|---|
| 公钥大小 | 256 字节 | 512 字节 | 64 字节 | 32 字节 |
| 签名大小 | 256 字节 | 512 字节 | 64 字节 | 64 字节 |
| 密文开销 | 256 字节 | 512 字节 | — | — |
ECC 的密钥和签名体积远小于 RSA,在物联网、移动端等带宽受限场景优势明显。
5.4 迁移建议
| 场景 | 当前方案 | 推荐方案 | 优先级 |
|---|---|---|---|
| TLS 证书 | RSA-2048 | ECC P-256/Ed25519 | 高 |
| SSH 密钥 | RSA-2048 | Ed25519 | 高 |
| 代码签名 | RSA-2048 | ECC P-256 | 中 |
| API 认证 | RSA-2048 | Ed25519 | 中 |
| 邮件加密 | RSA-2048 | Curve25519 + Ed25519 | 低 |
迁移时注意兼容性:部分旧客户端不支持 ECC 证书。建议过渡期同时支持 RSA 和 ECC 证书(双证书部署)。
六、非对称加密在真实系统中的应用
6.1 SSH 身份认证
每次 ssh user@server 背后都在使用非对称加密:
# 生成 Ed25519 SSH 密钥(推荐)ssh-keygen -t ed25519 -C "your@email.com"# 生成 RSA SSH 密钥(兼容旧系统)ssh-keygen -t rsa -b 4096 -C "your@email.com"6.2 Git 提交签名
# 配置 Git 使用 SSH 签名git config --global gpg.format sshgit config --global user.signingkey ~/.ssh/id_ed25519.pubgit config --global commit.gpgsign true
# 签名提交与标签git commit -S -m "Signed commit"git tag -s v1.0.0 -m "Signed release"
# 验证签名git verify-commit HEADgit verify-tag v1.0.06.3 代码签名与软件分发
# Python: 使用 Ed25519 对软件包签名from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
signing_key = Ed25519PrivateKey.generate()verify_key = signing_key.public_key()
with open("release-v1.0.tar.gz", "rb") as f: package_data = f.read()signature = signing_key.sign(package_data)
# 用户验证try: verify_key.verify(signature, package_data) print("签名验证通过,未被篡改")except Exception: print("签名验证失败,文件可能被篡改!")六·附、实践:RSA 与 ECC 密钥生成与加密
在前面六节中,从理论层面理解了非对称加密的数学基础、算法原理和性能对比。但密码学从来不是纸上谈兵的学科——只有亲手生成密钥、加密解密、观察输出,才能真正建立对安全工程的直觉。本节将带你用 OpenSSL 和 Python 逐步完成 RSA 与 ECC 的核心操作。
在动手之前,先回顾这段技术演进的历史脉络。
对称加密体系有一个绕不过去的困境:加密和解密使用同一个密钥,双方必须先安全地共享密钥才能通信。但在开放网络中,从未谋面的两个人如何安全地传递密钥?这就是著名的密钥分发问题。1976 年,Whitfield Diffie 和 Martin Hellman 在划时代论文《New Directions in Cryptography》中首次提出了公钥密码学的概念——使用一对密钥,公钥加密、私钥解密,从根本上解决了密钥分发困境。这一思想被誉为”密码学史上最伟大的革命”。
仅仅一年后的 1977 年,MIT 的 Ron Rivest、Adi Shamir 和 Leonard Adleman 提出了 RSA 算法——第一个可用的公钥加密方案。RSA 基于大整数分解的困难性,用简单的数学(模幂运算)实现了惊人的安全保证。此后二十年间,RSA 几乎成为非对称加密的代名词,广泛部署于 SSL/TLS、PGP、SSH 等协议中。
然而 RSA 的密钥长度随安全等级增长过快:128 位安全需要 3072 位密钥,256 位安全则需要 15360 位。1985 年,Neal Koblitz 和 Victor Miller 各自独立提出了椭圆曲线密码学(ECC)——用椭圆曲线上的离散对数问题替代大数分解,以更短的密钥达到同等安全等级。进入 21 世纪,Daniel J. Bernstein 设计的 Curve25519 和 Ed25519 进一步解决了 NIST 曲线的实现陷阱问题,凭借高性能、安全实现和抗侧信道攻击的特性,成为现代密码学的首选。今天,TLS 1.3 强制使用 ECDHE,SSH 默认推荐 Ed25519,Let’s Encrypt 默认签发 ECC 证书——ECC 已成为非对称加密的主流。
附.1 前置知识
- OpenSSL 3.0+ 命令行工具(本文基于 OpenSSL 3.0.13)
- Python 3.8+ +
cryptography库(本文基于 Python 3.13.0 + cryptography 43.0.3) - 理解本章前六节的理论内容,特别是 RSA 填充方案(OAEP)、ECDH 密钥交换和混合加密的概念
注意:本节所有密钥均为演示用途生成,绝不可用于生产环境。演示私钥已完整展示以便理解格式,生产环境中私钥必须严格保护(权限 600、存储于 HSM/KMS)。
附.2 RSA 密钥生成与加密解密
在第二章中学习了 RSA 的数学原理:密钥生成的核心是选择两个大素数 、,计算模数 。OpenSSL 将这些步骤封装为一条命令,实际操作一遍。
1.1 生成 RSA-2048 密钥对
# 生成 RSA-2048 私钥(PKCS#8 格式)openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa_private.pem..................................................................................................+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+.+......+......+...+.....+....+..+...+......+.+........+.+......+......+..+...+...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.....+...+........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+.+......+...+..+.......+..+.+..+...+.........+...+......+.+...+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+..............+...+.+.....+......+.+.....+...+....+..+......+...+....+......+.................+.........+...+.+..+........................+.........+.......+...+..+....+...+..+......+...+.......+............+.....+................+.....+.+........+...............+.............+..+.+..+....+......+.....+.............+.....+..........+..............+.+............+..+...+..........+........+.+........+.+..+...+.+...+..+.........+.+.....+.+...+.........+.....+.+...........+...+......................+......+..+.+.....+.+....................+...+..........+...+.....+.......+.....+............+...+.......+..............+.........+......+......+..................+...+.........+.+...+........................+..+.............+...+...+..+....+...+...........+.......+.....+...+.+......+.....+.......+...........+....+..+.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++# 从私钥导出公钥openssl rsa -in rsa_private.pem -pubout -out rsa_public.pemwriting RSA key查看生成的密钥文件内容:
# 查看私钥(演示用途,生产环境切勿展示!)cat rsa_private.pem-----BEGIN PRIVATE KEY-----MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLQtG6smr5pJMNhUYc8QlHRjLD/gEyzm5OA7QSoBfpgS7aH8RgPTyGyHadkwBeARIh6h3REttlh3unCPEiltDV3EH1IS9lubbB4eglfuN7tPpqyZ118m7nzyF1IsCJ+c3TGrXW+q3LjLXKNW9ea/S1ufhhkvkMQ3TZtX5ODIgZGtybY2h5hc3A+FH5i6z6dbC42GNzesp96jWU3cSVeo/19Wu5Bdy2E6hEsdsynyCs7gB7B60cVsX8WBzERDzmXUzrOQHwas/pT28dLUboYbddhhRaTQA1WvihacYEL7dru+6OysgZ94ZRUxAjK+l9FtSF9+VdfsuNdnET6sNpo0VNAgMBAAECggEACguwX+DHhrUKi4WFtJJ5GeuvQJ6CIqevLe3i7XS6GdFHpoIha3w7TJIFwr3sK7kz0zdF5/pdm4P5qYS4njQD+D3diKbuN8HY9XYbyhaeWZPVOIfkX1ru/kfvaGGiUG9nK8LLL+fNxRycM3U2nt0JCB47116zLlb7JHNi3aqi41Ko69UrtFHRRz7Gfw6wP2nVyvx940RI4qHsKaRaBw5QDTCBTD8Q4+5xeqxSoxjUNR60tjmyr6MhCwKCcuoEFGrbnlqgJsDuYUZFDJBt+lI6fAGkOpm1AlqI6h35pG0NbNz34xUVfFkZ4Ri40udpShs9Z2XMquebbCPmN4s5UiyPoQKBgQD3ExhTQ31SpLxadWnFu2WI8gI5N9+uk7/nr831J3fNf63yQLjIMwhMx/9/quGsc8yNp9qGnJMACtSWfm9zJeQSic21A2nBEgz44HlUf0nktnIQwJtS1rD8aOem9Cn8LcO/CobsG0PJrPwOxSfe2ZOhv6eTeFMFHrfhNwMN0+muOQKBgQDSmosjotNSKg+6Ce1R292OOPFxTlLvvV3aOCryLHZhwqnqbt3xk+6OODAAXiFA2nOUvLKSR1jcnbDvMziIbumdjg47+2HeDxZVczNPsY2ul+SnB3ssOA0TfCwl/HIGBHqJdNruxYMcf3YStLNUawnWLBjUuXEkvk8XiLI2TePPtQKBgA3PPGnS+Ns4p+pqa5oIqlkoJaWrEM6CJZS+h8VvCkwrGb7TjUBrUchw2T/6fhiUdFdqjVU+xRyYfl9W+8W01XWPzDaU1UFcT+tNKJrnbmrhI6j6CvRrWYMSHfh/5yTYLfusi/QAZh6Mk54DuQHEqXK98pDUhddgayikWYBZWtNJAoGBAL1xVwfPR9f5DfyDQX8JD9sHTehmVCRjazEIpVkCtdpqiP3gjLTqJQS5djRed/smhiGdO+RpWX3aO3Zv1MKlQm//gL5j+j0NxUFIRzfAt3n+GChHinzUTq7gW7Yvx0Gydad//hAaiz3vSf65nTaqskZ9lDiGN0ieB/dbdzNyDSKhAoGBAMj7YE9rv9Aci+nkjAy72mq1NiGg7J1ogS/JRzAStqpwvJZHbqQv6H2xF573WRSwOSC2pZJmztfC0w8MmF/BL9Kxxoob6HxWyI9LX6ffxeFyYOtna324PFDNchz/HGWtNl/VFrb76j0TUsnZdWZIea0jM/aYtE6vRxPwL08TB+WR-----END PRIVATE KEY-----# 查看公钥cat rsa_public.pem-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0LRurJq+aSTDYVGHPEJR0Yyw/4BMs5uTgO0EqAX6YEu2h/EYD08hsh2nZMAXgESIeod0RLbZYd7pwjxIpbQ1dxB9SEvZbm2weHoJX7je7T6asmddfJu588hdSLAifnN0xq11vqty4y1yjVvXmv0tbn4YZL5DEN02bV+TgyIGRrcm2NoeYXNwPhR+Yus+nWwuNhjc3rKfeo1lN3ElXqP9fVruQXcthOoRLHbMp8grO4AewetHFbF/FgcxEQ85l1M6zkB8GrP6U9vHS1G6GG3XYYUWk0ANVr4oWnGBC+3a7vujsrIGfeGUVMQIyvpfRbUhfflXX7LjXZxE+rDaaNFTQIDAQAB-----END PUBLIC KEY-----# 查看密钥详细信息openssl rsa -in rsa_private.pem -text -noout | head -3Private-Key: (2048 bit, 2 primes)modulus: 00:cb:42:d1:ba:b2:6a:f9:a4:93:0d:85:46:1c:f1:注:
genpkey生成的是 PKCS#8 格式私钥(BEGIN PRIVATE KEY),兼容性优于旧版genrsa生成的 PKCS#1 格式(BEGIN RSA PRIVATE KEY)。新项目推荐统一使用 PKCS#8。
1.2 使用 RSA-OAEP 加密与解密
正如第二章所述,RSA 加密必须使用 OAEP 填充——PKCS#1 v1.5 已被证明不安全。下面用刚才生成的密钥对加密一条消息:
# 准备明文echo "Hello, RSA-OAEP!" > plaintext.txt
# 用公钥加密(RSA-OAEP + SHA-256)openssl pkeyutl -encrypt \ -pubin -inkey rsa_public.pem \ -in plaintext.txt \ -out ciphertext.bin \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256# 查看密文信息wc -c ciphertext.binxxd -l 64 ciphertext.bin256 bytes00000000: 2c37 3e58 3396 385e b2fa bbc8 3718 37e9 ,7>X3.8^....7.7.00000010: 2a9e 46e2 087f 8713 7e8f 55b6 70e9 9ce2 *.F.....~.U.p...00000020: 9763 2493 6558 b6ed 95bc cbe2 35d2 8a83 .c$.eX......5...00000030: e714 2716 ea8b a184 69e9 af38 01f1 e7f7 ..'.....i..8....注:RSA-2048 的密文固定为 256 字节(2048 位),无论明文多短。这就是 RSA 不适合加密大量数据的原因之一。
# 用私钥解密openssl pkeyutl -decrypt \ -inkey rsa_private.pem \ -in ciphertext.bin \ -out decrypted.txt \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256
# 验证解密结果cat decrypted.txtHello, RSA-OAEP!解密成功!回顾一下刚才的操作流程:公钥加密 → 产生 256 字节密文 → 私钥解密 → 恢复原始明文。这正是第一章中”加密通信”模式的实际体现。
附.3 ECC 密钥生成
第三章中了解到,ECC 以更短的密钥达到与 RSA 同等的安全等级。用 OpenSSL 生成一条 ECC 密钥,直观感受密钥大小的差异。
# 生成 ECC 密钥(P-256 曲线,即 prime256v1)openssl ecparam -name prime256v1 -genkey -noout -out ecc_private.pem# 从私钥导出公钥openssl ec -in ecc_private.pem -pubout -out ecc_public.pemread EC keywriting EC key# 查看 ECC 私钥详情openssl ec -in ecc_private.pem -text -nooutPrivate-Key: (256 bit)priv: b5:2a:6f:b0:ce:7d:67:a9:16:85:20:00:c8:6f:8a: 44:c6:0c:74:4c:06:e9:c4:a3:94:28:b4:6e:c7:ec: 4d:54pub: 04:ec:2f:8b:ff:66:2c:c6:9c:a6:b2:f0:f8:79:2d: 5a:51:7a:13:d1:70:3d:4d:f4:b8:49:41:4a:a3:78: c3:2b:98:7a:80:3d:dd:a2:49:37:d1:49:c6:03:ad: 66:d5:f3:45:ee:4f:fe:d8:f9:de:49:31:92:31:55: c5:5c:9c:34:57ASN1 OID: prime256v1NIST CURVE: P-256# 查看 ECC 公钥cat ecc_public.pem-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7C+L/2YsxpymsvD4eS1aUXoT0XA9TfS4SUFKo3jDK5h6gD3dokk30UnGA61m1fNF7k/+2PneSTGSMVXFXJw0Vw==-----END PUBLIC KEY-----现在对比 RSA 和 ECC 的密钥文件大小:
echo "RSA 私钥: $(wc -c < rsa_private.pem) bytes"echo "RSA 公钥: $(wc -c < rsa_public.pem) bytes"echo "ECC 私钥: $(wc -c < ecc_private.pem) bytes"echo "ECC 公钥: $(wc -c < ecc_public.pem) bytes"RSA 私钥: 1704 bytesRSA 公钥: 451 bytesECC 私钥: 227 bytesECC 公钥: 178 bytesECC 的私钥仅为 RSA 的 1/7.5,公钥仅为 RSA 的 1/2.5——这就是第三章”为什么 ECC 密钥更短”的直观证据。在物联网设备、移动端等带宽和存储受限的场景中,这个差异意义重大。
注:ECC(P-256)与 RSA-2048 提供相近的安全等级(约 112-128 位),但 ECC 的密钥体积小得多。如果需要 128 位安全,RSA 需要 3072 位密钥,而 ECC 只需 256 位。
附.4 ECDH 密钥交换
第四章中学习了 Diffie-Hellman 密钥交换的原理和 ECDHE 在 TLS 中的核心地位。现在用 Python 的 cryptography 库实际演示 ECDH 密钥交换过程——Alice 和 Bob 如何在不安全的信道上协商出共享密钥:
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKeyfrom cryptography.hazmat.primitives import serialization
# Alice 生成密钥对alice_private = X25519PrivateKey.generate()alice_public = alice_private.public_key()
# Bob 生成密钥对bob_private = X25519PrivateKey.generate()bob_public = bob_private.public_key()
# 序列化公钥用于传输alice_pub_bytes = alice_public.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)bob_pub_bytes = bob_public.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
print(f"Alice 公钥 (hex): {alice_pub_bytes.hex()}")print(f"Bob 公钥 (hex): {bob_pub_bytes.hex()}")print(f"公钥长度: {len(alice_pub_bytes)} bytes")print()
# ECDH 密钥交换alice_shared = alice_private.exchange(bob_public)bob_shared = bob_private.exchange(alice_public)
print(f"Alice 计算的共享密钥 (hex): {alice_shared.hex()}")print(f"Bob 计算的共享密钥 (hex): {bob_shared.hex()}")print(f"共享密钥长度: {len(alice_shared)} bytes")print(f"双方共享密钥一致: {alice_shared == bob_shared}")运行结果:
Alice 公钥 (hex): b6f855339e6ecd37f1d5d1062610e98c35ce49054fa97a9f7a31c543453d6417Bob 公钥 (hex): dbc96c20304a67eab400103fe2a282ef7b428f79f61bfaf40750684a60ab192b公钥长度: 32 bytes
Alice 计算的共享密钥 (hex): f097fc615fb6d46459fe9d670e9187cf24f345e2418a7c0e15c4286f67ff6525Bob 计算的共享密钥 (hex): f097fc615fb6d46459fe9d670e9187cf24f345e2418a7c0e15c4286f67ff6525共享密钥长度: 32 bytes双方共享密钥一致: True注意几个关键点:
- Curve25519 的公钥仅 32 字节,比 P-256 的 64 字节(未压缩)还短,比 RSA-2048 的 256 字节更是小了一个数量级
- 双方从未传输私钥,仅交换了公钥,却计算出了相同的共享密钥——这就是 ECDH 的数学魔法
- 每次运行结果不同,因为密钥对是临时生成的(Ephemeral),这正是前向安全的保证
注意:原始共享密钥不应直接用作对称加密密钥,必须经过 KDF(如 HKDF)派生。直接使用原始共享密钥可能导致微妙的安全问题——下一节的混合加密演示将展示正确的做法。
附.5 混合加密:ECDH + AES-GCM
第一章中强调了”非对称加密从不直接加密大量数据”,实际系统使用混合加密。第五章的对比也表明,RSA/ECC 的价值在于密钥协商,而非数据加密。现在把 ECDH 和 AES-GCM 组合起来,实现一个完整的混合加密流程——这正是 TLS、PGP、Signal 等协议的核心工作方式:
import osfrom cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKeyfrom cryptography.hazmat.primitives.kdf.hkdf import HKDFfrom cryptography.hazmat.primitives import hashes, serializationfrom cryptography.hazmat.primitives.ciphers.aead import AESGCM
# ============================================================# 第一阶段:ECDH 密钥交换(协商对称密钥)# ============================================================print("=" * 60)print("第一阶段:ECDH 密钥交换")print("=" * 60)
# Alice 生成临时密钥对alice_private = X25519PrivateKey.generate()alice_public = alice_private.public_key()
# Bob 生成临时密钥对bob_private = X25519PrivateKey.generate()bob_public = bob_private.public_key()
alice_pub_bytes = alice_public.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)bob_pub_bytes = bob_public.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
print(f"Alice 公钥: {alice_pub_bytes.hex()}")print(f"Bob 公钥: {bob_pub_bytes.hex()}")print()
# 双方交换公钥,计算共享密钥alice_raw_shared = alice_private.exchange(bob_public)bob_raw_shared = bob_private.exchange(alice_public)assert alice_raw_shared == bob_raw_sharedprint(f"原始共享密钥: {alice_raw_shared.hex()}")print()
# 使用 HKDF 从共享密钥派生 AES-256 密钥# 注意:永远不要直接使用原始共享密钥!hkdf = HKDF( algorithm=hashes.SHA256(), length=32, # AES-256 需要 32 字节密钥 salt=None, info=b"hybrid-encryption-demo",)aes_key = hkdf.derive(alice_raw_shared)print(f"HKDF 派生的 AES-256 密钥: {aes_key.hex()}")print()
# ============================================================# 第二阶段:AES-GCM 加密数据# ============================================================print("=" * 60)print("第二阶段:AES-GCM 对称加密")print("=" * 60)
plaintext = b"Hello, hybrid encryption! This message is protected by ECDH + AES-256-GCM."print(f"明文: {plaintext.decode()}")print(f"明文长度: {len(plaintext)} bytes")print()
# Alice 使用派生密钥加密aesgcm = AESGCM(aes_key)nonce = os.urandom(12) # AES-GCM 推荐 12 字节 nonceciphertext = aesgcm.encrypt(nonce, plaintext, None)
print(f"Nonce (hex): {nonce.hex()}")print(f"密文+Tag (hex): {ciphertext.hex()}")print(f"密文+Tag 长度: {len(ciphertext)} bytes")print(f" 其中密文: {len(plaintext)} bytes")print(f" 其中认证标签: 16 bytes (AES-GCM 固定)")print()
# Bob 使用相同的派生密钥解密aesgcm_bob = AESGCM(aes_key)decrypted = aesgcm_bob.decrypt(nonce, ciphertext, None)
print(f"Bob 解密结果: {decrypted.decode()}")print(f"解密成功: {decrypted == plaintext}")print()
# ============================================================# 篡改检测演示# ============================================================print("=" * 60)print("篡改检测:AES-GCM 认证标签验证")print("=" * 60)
# 模拟密文被篡改tampered = bytearray(ciphertext)tampered[0] ^= 0xFF # 翻转第一个字节try: aesgcm_bob.decrypt(nonce, bytes(tampered), None) print("错误:篡改密文未被检测到!")except Exception as e: print(f"检测到篡改!解密失败: {type(e).__name__}") print("AES-GCM 的认证标签成功阻止了篡改攻击。")运行结果:
============================================================第一阶段:ECDH 密钥交换============================================================Alice 公钥: 18709306e16fb52c2656efa7d929d41163127ace3ddcf7a1dda5f2d1d9a68377Bob 公钥: a723dd1fb47cf2a0e78718dbbd88142d5d6d1d6684f3508f02cf599f47faa303
原始共享密钥: 605b03e077ae960c3dbf2f52088fe1b49ac73788f1b9828ccb1dfdc5b5b65c25
HKDF 派生的 AES-256 密钥: e5cf9be844f2ff60984982a23ba813e36ae8b3666282fb693890d1523c48df3f
============================================================第二阶段:AES-GCM 对称加密============================================================明文: Hello, hybrid encryption! This message is protected by ECDH + AES-256-GCM.明文长度: 74 bytes
Nonce (hex): 8b2b4f2b99fa528a93391090密文+Tag (hex): 04928f5a9a432585063cacab6034b8712ed231c04a734026102d1630290094c150bddba7eaa879c4d9c3ff1d9e465c481b563328c9b398f319a3bd0bbe13758cc33046879eecabfffa0a2fae41f9e612a3d76fec856d8c6ecd36密文+Tag 长度: 90 bytes 其中密文: 74 bytes 其中认证标签: 16 bytes (AES-GCM 固定)
Bob 解密结果: Hello, hybrid encryption! This message is protected by ECDH + AES-256-GCM.解密成功: True
============================================================篡改检测:AES-GCM 认证标签验证============================================================检测到篡改!解密失败: InvalidTagAES-GCM 的认证标签成功阻止了篡改攻击。这个演示完整展示了混合加密的两个阶段:
- ECDH 协商密钥:Alice 和 Bob 各自生成临时密钥对,交换公钥后计算出相同的共享密钥,再通过 HKDF 派生出 AES-256 密钥。窃听者即使截获了双方的公钥,也无法计算共享密钥(这就是 ECDLP 的困难性保证)。
- AES-GCM 加密数据:用派生的对称密钥加密实际数据。AES-GCM 同时提供机密性和完整性——任何对密文的篡改都会在解密时被检测到(
InvalidTag)。
注:完整的混合加密方案还需要将 Alice 的公钥和 nonce 随密文一起传输给 Bob,以便 Bob 执行 ECDH 和解密。实际协议(如 TLS)还包含证书验证、握手完整性校验等步骤。
附.6 实践小结
通过以上四个实操步骤,我们亲手完成了非对称加密的核心操作。下表总结了 RSA 与 ECC 在实践中的关键差异:
| 维度 | RSA-2048 | ECC P-256 / Curve25519 |
|---|---|---|
| 密钥生成速度 | 慢(~1s) | 极快(~1ms) |
| 私钥文件大小 | ~1700 bytes | ~230 bytes |
| 公钥文件大小 | ~450 bytes | ~180 bytes(P-256)/ 32 bytes(Curve25519) |
| 加密能力 | 可直接加密(≤245 bytes) | 不直接加密,仅用于密钥协商 |
| 加密填充 | OAEP(必须) | 不适用 |
| 密钥交换 | RSA-KM(无前向安全) | ECDHE(前向安全) |
| 混合加密模式 | RSA-OAEP 加密对称密钥 | ECDH 协商 + HKDF 派生对称密钥 |
| 安全等级 | ~112 位 | ~128 位 |
| 推荐场景 | 兼容旧系统、证书签名 | 新系统首选(密钥交换、签名) |
核心结论:新系统应优先选择 ECC——更短的密钥、更快的速度、更小的传输开销,且天然支持前向安全的密钥交换。RSA 仍在证书签名和兼容性场景中占有一席之地,但加密和密钥交换已逐步让位于 ECC。
七、总结
上一章建立了对称加密与 AEAD的认知框架。
7.1 核心要点
| 维度 | 关键要点 |
|---|---|
| RSA | 大数分解,OAEP 加密,PSS 签名,至少 2048 位 |
| ECC | 椭圆曲线,密钥短性能好,Curve25519/Ed25519 推荐 |
| ECDH | 密钥交换,前向安全,TLS 1.3 核心组件 |
| ECDSA/EdDSA | 数字签名,EdDSA 确定性更安全 |
| 密钥长度 | RSA-2048 ≈ ECC-256 ≈ AES-128 |
| 混合加密 | RSA/ECC 加密对称密钥,AES 加密数据 |
7.2 选型决策流程
7.3 最佳实践清单
- 新系统优先选择 ECC(Curve25519/Ed25519)
- RSA 加密使用 OAEP 填充,签名使用 PSS 填充
- 密钥交换使用 ECDHE,确保前向安全
- 从共享密钥派生对称密钥时使用 HKDF
- RSA 密钥至少 2048 位,推荐 3072 位
- 不要使用 PKCS#1 v1.5 填充
- 不要直接用非对称加密加密大量数据
- 不要在 ECDSA 中重复使用随机数 k
- 不要使用静态 DH 密钥交换
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






