3. RSA 算法原理:密钥生成、加解密、签名验签与数学基础
RSA 算法,说白了一句话:用两个大质数算出一个公钥和一个私钥,公钥加密的东西只有私钥能解开,私钥签名的东西只有公钥能验证。听起来简单,但落地时坑不少。我最早接触 RSA 是在一个金融支付项目里,当时被要求做双向签名验签,结果因为密钥长度选错,性能直接崩了……嗯,从那以后我对 RSA 的每个参数都格外敏感。
3.1 RSA 密钥生成:核心步骤
密钥生成是 RSA 的起点。我个人习惯把流程拆成五步,每一步都对应一个数学操作,但咱们先不堆公式,先看流程。
- 选两个大质数 p 和 q:长度至少 1024 位,实际生产环境我建议 2048 位起步。曾经有个客户为了省性能用了 512 位,结果被暴力破解了……
- 计算 n = p × q:n 就是公钥和私钥都依赖的模数,长度决定了 RSA 的安全强度。
- 计算欧拉函数 φ(n) = (p-1)(q-1):这个值要严格保密,泄露了就等于私钥丢了。
- 选公钥指数 e:常用 65537(0x10001),为什么?因为它是质数,且二进制只有两个 1,加密效率高。
- 计算私钥指数 d:d 是 e 关于 φ(n) 的模逆元,即 e × d ≡ 1 (mod φ(n))。
密钥长度对照表(我整理的生产环境推荐值)
| 密钥长度 | 安全等级 | 适用场景 | 性能表现 |
|---|---|---|---|
| 1024 位 | 已不安全 | 仅用于兼容旧系统 | 快 |
| 2048 位 | 当前标准 | 企业级签名验签、TLS 证书 | 中等 |
| 4096 位 | 高安全 | 金融、政务、CA 根证书 | 慢(约 2048 位的 4-8 倍) |
⚠ 避坑指南:我曾经见过有人直接用 OpenSSL 默认参数生成密钥,结果 p 和 q 太接近,导致 n 容易被因式分解。生成时一定要加 -check 参数,或者用硬件安全模块(HSM)来保证随机性。
3.2 RSA 加密与解密:数学原理简述
加密解密的数学原理其实就两个公式:
- 加密:c = me mod n(m 是明文,c 是密文)
- 解密:m = cd mod n
为什么会这样?因为欧拉定理保证了 me×d ≡ m (mod n)。说白了,就是指数运算的“可逆性”。你想想看,公钥 (n, e) 加密后,只有知道私钥 d 的人才能还原,其他人就算拿到 n 和 e,也解不开——因为不知道 φ(n) 就求不出 d。
但这里有个实际限制:RSA 不能直接加密大块数据。明文 m 必须小于 n,否则模运算会丢失信息。我在项目中通常的做法是:用 RSA 加密对称密钥(比如 AES 的 256 位密钥),再用对称密钥加密实际数据。这叫“混合加密”,既安全又高效。
💡 经验之谈:RSA 加密时,明文长度不能超过密钥长度减去填充开销。比如 2048 位密钥,PKCS#1 v1.5 填充后最多加密 245 字节,OAEP 填充后更少。别问我怎么知道的——线上出过 bug 才知道的。
3.3 RSA 签名与验签:流程与区别
签名验签和加密解密正好相反:私钥签名,公钥验签。我经常跟团队说:加密是“公钥锁,私钥开”;签名是“私钥签,公钥验”。
签名流程:
- 对原始数据做哈希(比如 SHA-256),得到摘要 h。
- 用私钥 d 对 h 做指数运算:s = hd mod n。
- 输出签名 s 和原始数据一起发送。
验签流程:
- 接收方用公钥 e 对签名做指数运算:h' = se mod n。
- 对原始数据做同样的哈希,得到 h。
- 比较 h' 和 h,相等则验签通过。
加密 vs 签名对比
| 操作 | 使用密钥 | 目的 | 典型场景 |
|---|---|---|---|
| 加密 | 公钥加密,私钥解密 | 保证机密性 | 传输对称密钥 |
| 签名 | 私钥签名,公钥验签 | 保证完整性和不可否认性 | 数字证书、代码签名 |
⚠ 常见错误:我曾经接手过一个项目,他们把签名和加密搞混了——用公钥去签名,结果验签永远失败。记住:签名只能用私钥,加密只能用公钥,方向不能反。
3.4 RSA 数学原理简述:欧拉定理与模逆元
虽然咱们是实战课,但数学原理得懂个大概,不然出了问题你都不知道怎么排查。我尽量用白话讲。
- 欧拉函数 φ(n):小于 n 且与 n 互质的正整数个数。对于 n = p×q,φ(n) = (p-1)(q-1)。
- 欧拉定理:如果 a 和 n 互质,则 aφ(n) ≡ 1 (mod n)。这是 RSA 能工作的根基。
- 模逆元:e × d ≡ 1 (mod φ(n)),d 就是 e 的模逆元。求 d 用的是扩展欧几里得算法,OpenSSL 底层就是这么算的。
你想想看,为什么 RSA 安全?因为从 n 分解出 p 和 q 是数学难题(大整数因式分解)。只要 p 和 q 足够大、足够随机,目前没有任何算法能在合理时间内破解。但量子计算机来了之后……嗯,那是另一回事了,咱们这门课先不展开。
3.5 RSA 完整流程图
下面这张图是我自己画的 RSA 密钥生成、加密解密、签名验签的全流程。你看一眼就能把整个体系串起来。
3.6 填充方案:为什么不能直接加密?
你可能会问:为什么 RSA 不能直接对原始数据做指数运算?因为没有填充的 RSA 是确定性的——同样的明文每次加密结果都一样,攻击者可以穷举。而且,小指数攻击、共模攻击等漏洞都会找上门。
我在项目中只用两种填充方案:
- PKCS#1 v1.5:兼容性好,但理论上存在 Bleichenbacher 攻击风险。老系统还在用,新系统我建议别碰。
- OAEP(Optimal Asymmetric Encryption Padding):带随机数的填充,安全性有严格证明。我个人习惯,所有新项目一律用 OAEP。
💡 选型建议:签名用 PSS(Probabilistic Signature Scheme)填充,加密用 OAEP 填充。别混用,混用过一次,调试了一整天才发现是填充模式不匹配。
3.7 性能与安全权衡
RSA 的性能瓶颈在指数运算上。2048 位密钥的私钥操作(解密或签名)比公钥操作(加密或验签)慢一个数量级。为什么?因为私钥指数 d 通常很大,而公钥指数 e 固定为 65537,二进制中 1 的个数少,计算快。
我常用的优化手段:
- 中国剩余定理(CRT):将私钥运算拆成 mod p 和 mod q 两部分,速度提升约 4 倍。几乎所有生产环境的 RSA 实现都用了 CRT。
- 缓存公钥指数:如果 e 固定为 65537,可以预计算一些中间值。
- 硬件加速:HSM 或 CPU 的 AES-NI 指令集对 RSA 也有加速效果。
⚠ 安全提醒:CRT 虽然快,但容易受到故障攻击(Fault Attack)。我曾经在测试中故意注入单比特错误,结果私钥被还原了……生产环境一定要用带校验的 CRT 实现,比如 OpenSSL 的 RSA_blinding_on()。