🏠 总目录📚 本教程 14 · RSA
📑 本页目录(点开跳转)

14 · RSA

30 分钟 | ⭐ 第一个公钥密码,也是坑最多的一个


🎯 一句话

RSA 用一个漂亮的不对称:把两个大素数乘起来很容易,把乘积拆回去极难而教科书上写的那个 RSA,直接用是完全不安全的 —— 这一章会讲清楚差在哪。

两个大素数p、q乘积 n公开相乘:毫秒级 ✅分解:2048 位时,已知算法要天文数字的时间 ❌同一个运算,两个方向的难度天差地别明文 M密文 C明文 MM^e mod n公钥 eC^d mod n私钥 d(要 p、q 才能算出)⭐ 「陷门」就是 p 和 q:知道它们,d 一步就求出来;不知道,就得先分解 n⚠️ 教科书版 RSA 是确定性的 → 同样的明文永远得到同样的密文 → 不满足 IND-CPA所以实际必须加随机填充(OAEP),签名则要用全域哈希
相乘毫秒级,分解却是天文数字 —— 同一个运算,两个方向难度天差地别。⭐ 「陷门」就是 p 和 q:知道它们私钥一步就求出来,不知道就得先分解 n。⚠️ 教科书版 RSA 是确定性的,不满足 IND-CPA —— 实际必须加 OAEP 随机填充。

🔑 一、算法

   【密钥生成】
   ① 选两个大素数 p、q(各 1536 bit)
   ② n = p·q                       ← 公开
   ③ φ(n) = (p−1)(q−1)             ← 保密!
   ④ 选 e,满足 gcd(e, φ(n)) = 1   ← 通常 e = 65537
   ⑤ d = e⁻¹ mod φ(n)              ← 私钥(扩展欧几里得,第 13 章)

   公钥 = (n, e)      私钥 = d(外加 p、q 用于 CRT 加速)

   【加解密】
   C = M^e mod n
   M = C^d mod n
📐 为什么解密能还原(想看再点)

由 $d = e^{-1} \bmod \varphi(n)$,存在整数 k 使 $ed = 1 + k\varphi(n)$。

情况 1:$\gcd(M, n) = 1$。由欧拉定理 $M^{\varphi(n)} \equiv 1 \pmod n$:

$$C^d = M^{ed} = M^{1+k\varphi(n)} = M\cdot(M^{\varphi(n)})^k \equiv M\cdot 1^k = M \pmod n$$

情况 2:$\gcd(M,n) \ne 1$(M 被 p 或 q 整除)—— 用中国剩余定理分别在 mod p 和 mod q 下验证,结论同样成立。∎

💡 核心就一句e 和 d 在指数上互为逆元,一乘一除正好回到原点。

💡 为什么 e = 65537

   e = 65537 = 2¹⁶ + 1 = 二进制 10000000000000001

   ⭐ 只有两个 1 → 平方-乘算法只需 17 次操作 → 加密极快
   ⭐ 又足够大 → 避免小指数攻击(见下)

   ⚠️ 历史上用过 e = 3,导致了真实的攻击

💀 二、教科书 RSA 为什么不能直接用

问题 ① 它是确定性的

   相同明文 → 相同密文

   ⭐ 直接违反 IND-CPA(第 8 章的第一个推论)

   💥 实际攻击:如果明文空间小(比如"是"/"否"、一个 4 位数验证码),
      攻击者把所有可能的明文都加密一遍,查表即可 💀

问题 ② 乘法同态性可被滥用

   ⭐ RSA 有一个"意外"的性质:

   Enc(M₁) · Enc(M₂) = M₁^e · M₂^e = (M₁·M₂)^e = Enc(M₁ · M₂)  mod n

   💥 攻击:攻击者想让你解密 C,但你拒绝
      他改为让你解密 C' = C · 2^e mod n(看起来是无关的密文)
      你返回 M' = 2M
      他除以 2 就得到 M ⭐

💥 这个攻击有个名字叫"盲化攻击",它也说明了 为什么绝对不能用同一个密钥对既加密又签名

问题 ③ 小指数攻击

   若 e = 3 且消息很短(M³ < n):
   → C = M³ 根本没有发生模运算
   → 攻击者直接开【三次方根】就还原了 💀

   ⭐ Håstad 广播攻击:同一条消息用 e=3 发给 3 个不同的人
     → 用中国剩余定理合并 3 个密文 → 直接解出 M

问题 ④ 共模 / 共因子

   💥 共模攻击:两个人用相同的 n、不同的 e
      → 任一方都能算出对方的私钥

   💥 共因子:如果两个 RSA 公钥的 n 恰好共享一个素数因子
      → gcd(n₁, n₂) 直接给出那个素数 → 两个密钥全废

   ⭐ 这不是理论:2012 年有研究扫描全网 TLS 证书,
     发现【数万个】RSA 公钥可以被这样直接分解 ——
     根因是嵌入式设备开机时熵不足(第 4 章)

✅ 三、正确用法:必须填充

加密用 OAEP

   OAEP = Optimal Asymmetric Encryption Padding

   把消息 + 【随机数】+ 结构化填充,经过两轮哈希混合,再做 RSA

   ✅ 引入随机性 → 满足 IND-CPA
   ✅ 结构化 → 破坏乘法同态
   ✅ 在随机预言机模型下可证 IND-CCA

💥 它取代的 PKCS#1 v1.5 有著名的 Bleichenbacher 攻击(1998): 服务器对"填充格式错误"返回不同响应 → 又是一个预言机 → 约百万次查询就能解密任意密文。 2017 年的 ROBOT 攻击发现,20 年后仍有大量主流网站没修好。

签名用 PSS

   ❌ 直接 σ = M^d mod n(教科书签名)
      → 攻击者可以:σ₁·σ₂ 是 M₁·M₂ 的合法签名 💀(同态性)
      → 也可以先选 σ,反算 M = σ^e,得到一个"合法"的签名 💀

   ✅ 先哈希再填充:σ = PSS(H(M))^d mod n

🔗 第 17 章会详细讲签名,包括 FDH 和 PSS 的区别。

# ✅ 正确用法
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes

key = rsa.generate_private_key(public_exponent=65537, key_size=3072)

ct = key.public_key().encrypt(
    b"secret",
    padding.OAEP(mgf=padding.MGF1(hashes.SHA256()),      # ⭐ 必须 OAEP
                 algorithm=hashes.SHA256(), label=None))

sig = key.sign(b"message",
    padding.PSS(mgf=padding.MGF1(hashes.SHA256()),        # ⭐ 必须 PSS
                salt_length=padding.PSS.MAX_LENGTH),
    hashes.SHA256())

⏱️ 四、侧信道:私钥能被"听"出来

攻击 原理
计时攻击 模幂的时间取决于 d 的比特 → 测时间恢复 d ⭐
功耗分析 平方和乘法的功耗曲线不同,肉眼可辨
Bellcore 故障攻击 CRT 解密中制造一次错误 → 一次错误签名就能分解 n
缓存攻击 窗口法的查表访问模式泄露指数位
   ✅ 防御:
   ├─ 恒定时间实现(不要写自己的 powmod)
   ├─ 【盲化】:算 (C·r^e)^d 再除以 r,让每次运算的输入随机化 ⭐
   └─ CRT 后验证签名(防 Bellcore)

🔑 这是第 1 章那条建议的具体理由不要自己实现密码算法 —— 数学写对了,实现照样能漏。


📉 五、RSA 今天的地位

   ✅ 仍然广泛存在:TLS 证书、代码签名、SSH、JWT

   ⚠️ 但新系统正在离开它:
   ├─ 密钥太大(3072 bit vs 椭圆曲线的 256 bit)⭐
   ├─ 签名/密钥生成慢
   ├─ 坑太多(填充、参数、侧信道)
   └─ 量子威胁:Shor 算法直接摧毁(第 23 章)

   ✅ 新项目的推荐:
   ├─ 签名  → Ed25519(第 16 章)
   ├─ 密钥交换 → X25519
   └─ 需要抗量子 → ML-KEM / ML-DSA(第 23 章)

🔗 和站内其他章的关系

相关的地方 和这一章的关系
第 13 章 φ(pq)=(p−1)(q−1) RSA 的全部秘密
第 13 章 欧拉定理 正确性证明
第 13 章 模逆元 d 的计算
第 8 章 确定性⟹不安全 教科书 RSA 的第一宗罪 ⭐
第 4 章 熵不足 共因子攻击的根因
第 7 章 padding oracle Bleichenbacher 是它的公钥版
AI 基础设施 · GPU 到底是什么 计时侧信道的来源就是那边讲 CPU 时列的那几样:分支预测、乱序执行、缓存——正因为执行时间随数据变化,私钥才能被"听"出来 ⭐
Claude 资料库 · 触达生产系统 你给生产 Agent 配的 OAuth / token 签名,底下跑的就是本章的 RSA(或 ECDSA);⚠️ 那一节的结论也一样:认证用标准件,别自己拼填充

✅ 检查点

  1. RSA 的密钥生成五步是什么?哪些必须保密?
  2. 为什么解密能还原明文?核心是哪一句?
  3. 为什么 e 通常取 65537?
  4. 教科书 RSA 的四个问题分别是什么?
  5. 乘法同态性怎么被用来攻击?这说明了什么设计原则?
  6. Håstad 广播攻击是怎么回事?
  7. 共因子攻击的根因是什么?它和第 4 章什么内容有关?
  8. OAEP 提供了哪三样东西?它取代的 PKCS#1 v1.5 有什么问题?
  9. Bellcore 故障攻击有多严重?怎么防?
  10. 新项目该用什么代替 RSA?
👀 答案
  1. ①选大素数 p、q ②n=pq ③φ(n)=(p−1)(q−1) ④选 e 与 φ(n) 互质 ⑤d=e⁻¹ mod φ(n)。p、q、φ(n)、d 都必须保密(知道 φ 就能算出 d)。
  2. 因为 ed = 1 + kφ(n),由欧拉定理 M^φ(n) ≡ 1,所以 C^d = M^(1+kφ(n)) = M·(M^φ(n))^k ≡ M。核心:e 和 d 在指数上互为逆元,一乘一除回到原点
  3. 65537 = 2¹⁶+1,二进制只有两个 1 → 平方-乘只需 17 次操作,加密极快;同时足够大,避免小指数攻击。
  4. 确定性(违反 IND-CPA,明文空间小时可枚举)②乘法同态性可被滥用 ③小指数攻击共模/共因子
  5. Enc(M₁)·Enc(M₂) = Enc(M₁·M₂)。攻击者让你解密 C·2^e,拿到 2M 后除以 2 得到 M(盲化攻击)。说明:绝对不能用同一个密钥既加密又签名
  6. 同一条消息用 e=3 发给 3 个不同的人,攻击者收集 3 个密文,用中国剩余定理合并后直接开三次方根解出 M。
  7. 嵌入式设备开机时熵不足,导致不同设备生成的 RSA 素数重复。gcd(n₁,n₂) 直接给出共享素数 → 两个密钥全废。和第 4 章的随机数失效是同一根因。2012 年扫描全网发现数万个这样的公钥。
  8. 随机性(满足 IND-CPA)②结构化填充(破坏乘法同态)③ROM 下可证 IND-CCA。PKCS#1 v1.5 有 Bleichenbacher 攻击:填充错误的不同响应构成预言机,约百万次查询解密任意密文;2017 年 ROBOT 攻击发现 20 年后仍有大量主流网站没修好
  9. 在 CRT 解密中制造一次错误,仅凭一次错误签名就能分解 n。防御:CRT 后验证签名结果再输出。
  10. 签名用 Ed25519,密钥交换用 X25519,需要抗量子用 ML-KEM / ML-DSA

🛑 可以停在这里

走神救援

RSA:n=pq,φ(n)=(p−1)(q−1),选 e 互质于 φ,d=e⁻¹ mod φ(n);C=M^e,M=C^d。正确性靠欧拉定理(ed=1+kφ(n) ⟹ e 和 d 在指数上互为逆元)。e=65537=2¹⁶+1:二进制只两个 1 → 17 次操作,又够大避免小指数攻击。⭐教科书 RSA 四宗罪:①确定性 ⟹ 违反 IND-CPA(明文空间小就直接枚举查表)②⭐乘法同态 Enc(M₁)·Enc(M₂)=Enc(M₁M₂) → 盲化攻击:让你解密 C·2^e 拿到 2M 再除以 2;说明绝不能同一密钥既加密又签名小指数攻击(e=3 且 M³<n 直接开三次方根;Håstad 广播攻击:同消息发给 3 人,CRT 合并即解)④共模/共因子(💥2012 年扫全网发现数万个 RSA 公钥可被 gcd 直接分解,根因是嵌入式设备开机熵不足)。✅必须填充:加密用 OAEP(随机性→IND-CPA、结构化→破坏同态、ROM下可证IND-CCA),它取代的 PKCS#1 v1.5 有💥Bleichenbacher 攻击(填充错误的不同响应=预言机,百万次查询解密任意密文;2017 ROBOT 发现 20 年后大量网站仍没修好);签名用 PSS(教科书签名可被 σ₁·σ₂ 伪造,也能先选 σ 反算 M)。侧信道:计时/功耗/缓存,⭐Bellcore 故障攻击——CRT 解密中一次错误就能分解 n,防御靠恒定时间实现 + 盲化 + CRT 后验签。现状:仍广泛存在但新系统在离开(密钥 3072 vs ECC 256、慢、坑多、Shor 直接摧毁)→ 新项目用 Ed25519 / X25519 / ML-KEM

下一节 👉 15-DH与ElGamal.md

打卡记录保存在你的浏览器里,首页能看到总进度