4、密钥交换算法(一):RSA密钥交换原理、Pre-Master Secret的生成与解密流程
做TLS握手的人都知道,RSA密钥交换曾经是的主流。你打开十年前的开源项目,全是RSA。为什么?因为它简单、成熟,你只需要一把公钥就能完成密钥协商。但后来大家发现它有个要命的短板——没有前向安全性。这个坑,我后面会细说。
4.1 RSA密钥交换的核心思想
说白了,RSA密钥交换就是客户端用服务器的RSA公钥加密一个随机值,服务器用自己的私钥解密出来。这个随机值就是Pre-Master Secret,后续所有会话密钥都源于它。
我记得刚接触TLS时,总觉得这不就是普通的公钥加密吗?有什么好讲的?后来真去抓包分析才发现,细节全在填充和随机数生成里。嗯,这里要注意。
4.2 Pre-Master Secret的生成
Pre-Master Secret只有48字节。它的结构非常固定:
| 字段 | 长度 | 说明 |
|---|---|---|
| Client Version | 2 字节 | 客户端支持的TLS最高版本号(例如 TLS 1.2 为 0x0303) |
| Random Data | 46 字节 | 由密码学安全随机数生成器产生的随机值 |
你看,版本号只有2字节,剩下的46字节必须纯随机。我曾经在一个项目中遇到过底层随机数生成器被截断的Bug——每次握手的前10个字节都一样。那真是噩梦,排查了三天才发现是OpenSSL的FIPS模块初始化顺序问题。所以,随机数的质量,我建议你用/dev/urandom或者系统的CryptGenRandom,别再信那些“自定义伪随机”了。
4.3 加密与解密流程
整个过程分两步走。我习惯用下图来帮助理解:
解密流程中,服务器端会做这几件事:
- 私钥解密:用RSA私钥把EncryptedPreMasterSecret还原成48字节明文。
- 版本校验:取出前2字节与ClientHello中声明的版本比对。如果不一致,很多实现会直接终止握手。
- 提取随机数据:后46字节就是随机种子,与客户端生成的Client Random、服务器生成的Server Random一起,通过PRF(伪随机函数)生成Master Secret。
// 示意:OpenSSL 中的解密处理(极简)
// EVP_PKEY_decrypt 会自动处理填充
int decrypt_pre_master(EVP_PKEY *pkey, const unsigned char *enc, size_t enc_len,
unsigned char *pms, size_t *pms_len) {
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_decrypt_init(ctx);
// 设置填充模式,通常为 PKCS1_OAEP_PADDING
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
EVP_PKEY_decrypt(ctx, pms, pms_len, enc, enc_len);
EVP_PKEY_CTX_free(ctx);
return 0;
}
填充模式之争:老协议(SSL 3.0 / TLS 1.0)常用 PKCS#1 v1.5 填充,但这个模式被Bleichenbacher攻击锤了十几年。TLS 1.2 开始推荐使用 RSAES-OAEP。我的建议——新项目一律用OAEP。
4.4 为什么RSA密钥交换逐渐被替代?
你想想看,RSA密钥交换最大的问题是什么?没有前向安全性(Perfect Forward Secrecy, PFS)。服务器私钥如果泄露,攻击者可以解密以前记录的所有会话。因为Pre-Master Secret是用公钥加密的,只要私钥在手,就能还原所有旧的Master Secret。
我曾经帮一家银行做安全审计,他们所有的HTTPS流量还是RSA密钥交换。我建议他们马上切到ECDHE。对方CTO还犹豫:“RSA用得好好的,换什么?”直到我演示了私钥泄露模拟——一个抓包文件里六个月的会话全被解密。第二天,排期就上了。
TLS_RSA_WITH_AES_128_GCM_SHA256 这类套件,请立即清理。它能帮你兼容老设备,但代价是你的会话没有PFS保障。
总结一下RSA密钥交换的优缺点:
| 优势 | 劣势 |
|---|---|
| 实现简单,所有TLS库都支持 | 无前向安全性(私钥泄露=所有会话泄露) |
| 客户端无需生成密钥对 | 不支持临时密钥协议,无法抵抗被动窃听 |
| 兼容性极好(旧设备、旧浏览器) | RSA密钥长度增加会加大计算开销 |