RSA 在现代系统中的角色:真实用途与为何被替代
Published: Sat Feb 01 2025 | Modified: Sat Feb 07 2026 , 9 minutes reading.
1. 为什么要关心这个问题?
你已经学会了 RSA 的数学基础。但如果你观察今天的实际系统:
- TLS 1.3 完全移除了 RSA 密钥交换
- 大多数网站使用 ECDSA 或 EdDSA 证书
- SSH 默认使用 Ed25519 密钥
- Signal、WhatsApp 使用 Curve25519
RSA 怎么了?它过时了吗?理解 RSA 的演变能帮助你为系统做出更好的选择。
2. RSA 的三种角色
RSA 技术上能做三件事,但现代用途很有限:
┌─────────────────────────────────────────────────────────────┐
│ 角色 1:直接加密 │
│ 状态:绝对不要用 │
│ 原因:大小限制、性能问题、安全问题 │
├─────────────────────────────────────────────────────────────┤
│ 角色 2:密钥交换(加密会话密钥) │
│ 状态:已废弃(TLS 1.3 移除了它) │
│ 原因:没有前向保密 │
├─────────────────────────────────────────────────────────────┤
│ 角色 3:数字签名 │
│ 状态:仍在使用(但 ECC 更受欢迎) │
│ 用途:代码签名、证书、遗留系统 │
└─────────────────────────────────────────────────────────────┘3. 为什么不能用 RSA 直接加密数据
大小限制
RSA-2048 只能加密:
最大原始数据:256 字节
使用 OAEP 填充后:214 字节
你的 1MB 文件?做不到。
你的 1KB JSON?还是做不到。性能灾难
# 基准测试:加密 1MB 数据
import time
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# 生成密钥
rsa_private = rsa.generate_private_key(public_exponent=65537, key_size=2048)
rsa_public = rsa_private.public_key()
aes_key = os.urandom(32)
data = os.urandom(1024 * 1024) # 1MB
# RSA:必须分成 190 字节的块(为了填充安全)
def rsa_encrypt_chunked(data):
chunks = [data[i:i+190] for i in range(0, len(data), 190)]
return [rsa_public.encrypt(
chunk,
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(), label=None)
) for chunk in chunks]
# AES-GCM:一次加密全部
def aes_encrypt(data):
nonce = os.urandom(12)
return AESGCM(aes_key).encrypt(nonce, data, None)
# 时间对比
start = time.time()
rsa_encrypt_chunked(data)
rsa_time = time.time() - start
start = time.time()
aes_encrypt(data)
aes_time = time.time() - start
print(f"RSA 分块: {rsa_time:.2f}s")
print(f"AES-GCM: {aes_time:.4f}s")
print(f"RSA 慢了 {rsa_time/aes_time:.0f} 倍")
# 典型输出:
# RSA 分块: 15.23s
# AES-GCM: 0.0021s
# RSA 慢了 7252 倍正确的方式:混合加密
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def hybrid_encrypt(public_key, plaintext: bytes) -> dict:
"""使用混合加密加密数据"""
# 1. 生成随机对称密钥
session_key = os.urandom(32)
# 2. 用对称密钥加密数据(快)
nonce = os.urandom(12)
ciphertext = AESGCM(session_key).encrypt(nonce, plaintext, None)
# 3. 用 RSA 加密对称密钥(小,所以快)
encrypted_key = public_key.encrypt(
session_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return {
'encrypted_key': encrypted_key,
'nonce': nonce,
'ciphertext': ciphertext
}
def hybrid_decrypt(private_key, encrypted: dict) -> bytes:
"""解密混合加密的数据"""
# 1. 用 RSA 解密对称密钥
session_key = private_key.decrypt(
encrypted['encrypted_key'],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# 2. 用对称密钥解密数据
plaintext = AESGCM(session_key).decrypt(
encrypted['nonce'],
encrypted['ciphertext'],
None
)
return plaintext4. TLS 中的 RSA 密钥交换(旧方式)
TLS 1.2 中的工作方式
客户端 服务器
│ │
│───── ClientHello ─────────────────>│
│ │
│<──── ServerHello + 证书 ───────────│
│ (包含 RSA 公钥) │
│ │
│ │
│ 客户端生成 PreMasterSecret │
│ 用服务器的 RSA 密钥加密 │
│ │
│───── ClientKeyExchange ───────────>│
│ (RSA 加密的密钥) │
│ │
│ 双方从 PreMasterSecret │
│ 派生会话密钥 │
│ │
│<════ 加密通信 ════════════════════>│致命缺陷:没有前向保密
问题所在:
1. 攻击者记录你所有的加密流量(存储很便宜)
2. 多年后,攻击者窃取了服务器的私钥
3. 攻击者解密所有历史流量
这叫做「现在收集,未来解密」
RSA 密钥交换意味着:
- 一个密钥泄露 = 所有过去的会话都被破解
- 没有前向保密TLS 1.3 的解决方案:只允许临时密钥交换
TLS 1.3 完全移除了 RSA 密钥交换
只允许 (EC)DHE - 临时 Diffie-Hellman
客户端 服务器
│ │
│───── ClientHello + KeyShare ──────>│
│ (临时 ECDH 公钥) │
│ │
│<──── ServerHello + KeyShare ───────│
│ (临时 ECDH 公钥) │
│ │
│ 双方计算共享密钥 │
│ 密钥用后即弃 │
│ │
│<════ 前向保密的加密 ═══════════════>│
即使服务器的私钥之后泄露,
过去的会话仍然安全!5. RSA 签名仍然存在
虽然 RSA 加密已被废弃,RSA 签名仍在使用:
当前用途:
├── 代码签名
│ ├── Windows Authenticode(RSA 常见)
│ ├── macOS codesign(RSA 或 ECDSA)
│ └── Android APK(正在转向 ECDSA)
│
├── TLS 证书
│ ├── 遗留:RSA 签名
│ ├── 现代:ECDSA 优先
│ └── 最新:EdDSA (Ed25519)
│
├── SSH 密钥
│ ├── 遗留:ssh-rsa(OpenSSH 8.8 已废弃)
│ ├── 现代:rsa-sha2-256, rsa-sha2-512
│ └── 推荐:ssh-ed25519
│
└── JWT/JWS
├── RS256, RS384, RS512 (RSA)
└── ES256, ES384, ES512 (ECDSA,推荐)RSA 签名示例
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
# 生成密钥对
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
public_key = private_key.public_key()
# 签名
message = b"Important document content"
signature = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# 验证
try:
public_key.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("签名有效!")
except Exception:
print("签名无效!")6. 真实世界的 RSA 漏洞
填充预言攻击(Bleichenbacher 攻击)
针对 PKCS#1 v1.5 填充的攻击:
1. 攻击者截获 RSA 密文
2. 向服务器发送修改过的密文
3. 服务器根据填充有效性给出不同响应
4. 经过数百万次查询后,攻击者恢复明文
著名案例:
- DROWN 攻击(2016)
- ROBOT 攻击(2017)
防御:使用 OAEP 填充,不要用 PKCS#1 v1.5时序攻击
# 有漏洞:解密过程中的时序泄露
def vulnerable_decrypt(ciphertext, private_key):
plaintext = rsa_decrypt(ciphertext, private_key)
if not valid_padding(plaintext): # 这个时间会变化!
raise PaddingError()
return plaintext
# 安全:常数时间操作
# 使用处理了这个问题的库实现常见实现错误
错误 1:使用不带填充的原始 RSA
C = M^e mod n
→ 乘法性质允许操纵
→ 攻击者可以伪造:C' = C × 2^e = (2M)^e
错误 2:小公开指数配合小消息
如果 M^e < n,攻击者直接计算 e 次方根即可
错误 3:相同消息发给多个接收者
当 e=3 且相同的 M 发给 3 个不同的 n 值时,
中国剩余定理可以恢复 M
错误 4:相关消息
用同一个密钥加密 M 和 M+1
→ Franklin-Reiter 攻击可以恢复两者7. RSA vs ECC 对比
属性 | RSA-2048 | ECC P-256
------------------+-----------------+------------------
密钥大小 | 256 字节 | 32 字节
签名大小 | 256 字节 | 64 字节
安全级别 | 112 位 | 128 位
密钥生成 | 慢(找质数) | 快
签名速度 | 中等 | 快
验证速度 | 快(e 小) | 中等
性能趋势 | 越来越差 | 保持良好
等效安全性:
RSA-3072(384 字节)≈ ECC P-256(32 字节)
RSA-15360(1920 字节)≈ ECC P-521(66 字节)8. 何时仍应使用 RSA
合理的使用场景
1. 遗留系统兼容性
- 旧系统只支持 RSA
- 政府/企业要求
2. 硬件约束
- 某些 HSM 针对 RSA 优化
- 某些智能卡只支持 RSA
3. 特定协议要求
- 某些签名 PDF 标准
- 某些企业 PKI 系统建议
新项目检查清单:
□ 密钥交换:使用 ECDH(X25519 优先)
□ 签名:使用 EdDSA (Ed25519) 或 ECDSA (P-256)
□ TLS:使用 TLS 1.3(没有 RSA 密钥交换)
□ SSH:使用 Ed25519 密钥
□ JWT:使用 ES256 代替 RS256
只有在以下情况使用 RSA:
□ 被遗留兼容性强制要求
□ 被外部法规要求
□ 硬件只支持 RSA9. 从 RSA 迁移的路径
TLS 证书迁移
步骤 1:生成新的 ECC 密钥
openssl ecparam -genkey -name prime256v1 -out ecdsa.key
步骤 2:创建 CSR
openssl req -new -key ecdsa.key -out ecdsa.csr
步骤 3:部署双证书(过渡期)
- 主要:ECDSA 证书
- 备用:RSA 证书(用于旧客户端)
步骤 4:监控并在安全时移除 RSASSH 密钥迁移
# 生成新的 Ed25519 密钥
ssh-keygen -t ed25519 -C "[email protected]"
# 添加到服务器的 authorized_keys
# 暂时保留 RSA 密钥以确保兼容性
# 测试 Ed25519 访问
ssh -i ~/.ssh/id_ed25519 user@server
# 确认无误后删除 RSA 密钥10. 常见误区
| 误区 | 现实 |
|---|---|
| 「RSA 被破解了」 | RSA 数学没问题,但有更好的替代品 |
| 「更长的密钥 = 更好」 | RSA 密钥增长是不可持续的 |
| 「RSA 加密是标准」 | 混合加密才是标准 |
| 「TLS 使用 RSA 加密」 | TLS 1.3 只用 RSA 做签名 |
| 「为了安全我应该用 RSA」 | 你应该用 ECC |
11. 本章小结
三点要记住:
RSA 不是用来直接加密的。 使用混合加密:RSA 加密对称密钥,对称密钥加密数据。
RSA 密钥交换缺乏前向保密。 TLS 1.3 移除了它。使用临时 (EC)DH 代替。
RSA 签名仍然有效,但 ECC 更好。 对于新项目,优先使用 Ed25519 或 ECDSA 而不是 RSA。
12. 下一步
RSA 服务了我们数十年,但它的密钥尺寸不断增长,而 ECC 保持紧凑。椭圆曲线密码学如何用更小的密钥达到相同的安全性?
在下一篇文章中:椭圆曲线密码学——曲线上的点如何能替代质因数分解,以及为什么密码学世界正在转向曲线。
