📑 本页目录(点开跳转)
07 · 工作模式
⏱ 26 分钟 | ⭐ 一张图就能看懂 ECB 为什么该死
🎯 一句话
分组密码一次只能加密 128 bit。长消息怎么办? 答案是「工作模式」—— 而选错模式,比选错算法后果严重得多。
🐧 一、ECB:那张著名的企鹅图
最直觉的做法:把消息切块,每块单独加密
C₁ = E_K(M₁)
C₂ = E_K(M₂)
C₃ = E_K(M₃)
...
为什么它是错的:
E_K 是【确定性】的
→ 相同的明文块 ⟹ 相同的密文块 ⭐
→ 明文的【结构】原样保留在密文里
著名的实验:把一张企鹅图片用 ECB 加密
原图: 🐧 清晰的企鹅
ECB后: 🐧 还是能看出企鹅的轮廓!只是颜色变成噪声
因为图片里大片相同颜色的区域 → 加密成大片相同的密文块
🔑 这也是第 8 章 IND-CPA 的第一个反例: 攻击者提交 m₀ = "AAAA...AAAA"(两个相同块)和 m₁ = "AAAA...BBBB"(两个不同块), 看密文的两个块是否相同就能百分之百分辨 —— 连一点点优势都不需要猜。
⭐ 一句话结论:ECB 在任何安全场景下都不该出现。 如果你在代码里看到
AES/ECB/PKCS5Padding,那基本就是一个 bug。
🔗 二、CBC:把前一块的密文混进来
C₀ = IV ← 随机初始向量
Cᵢ = E_K(Mᵢ ⊕ Cᵢ₋₁)
解密:Mᵢ = D_K(Cᵢ) ⊕ Cᵢ₋₁
M₁ M₂ M₃
│ │ │
⊕◄─IV ⊕◄──┐ ⊕◄──┐
│ │ │ │ │
[E] [E] │ [E] │
│ │ │ │ │
C₁──────────┘ C₂──┘ C₃
💡 人话:每一块加密前先和上一块的密文异或 —— 让相同的明文块产生不同的密文块。
| 性质 | CBC |
|---|---|
| 加密 | ❌ 不能并行(Cᵢ 依赖 Cᵢ₋₁) |
| 解密 | ✅ 可以并行(Mᵢ 只需要 Cᵢ 和 Cᵢ₋₁) |
| IV 要求 | ⭐ 必须随机且不可预测(不只是唯一!) |
| 需要填充 | ✅ 是 —— 这是它最大的麻烦来源 |
💥 CBC 的两个真实事故
① BEAST 攻击(2011)—— IV 可预测
TLS 1.0 用【上一条消息的最后一个密文块】当下一条的 IV
→ IV 变得【可预测】
→ 攻击者可以构造明文,验证他对某个明文块的猜测 ⭐
→ 逐字节破解 cookie
🔑 这就是为什么 CBC 的 IV 必须"随机且不可预测",而不只是"唯一"。 对比 CTR 模式的 nonce:那个只需要唯一。这是两个不同的要求。
② Padding Oracle 攻击 —— 填充错误泄露信息
CBC 需要填充到分组边界,解密后要检查填充是否合法
如果服务器对"填充错误"和"其他错误"返回不同的响应
(不同的错误码、不同的响应时间、甚至只是不同的日志)
→ 攻击者获得了一个【解密预言机】⭐
→ 每次询问泄露 1 bit,约 256 次询问就能解出 1 个字节
→ 完整解密任意密文,【完全不需要密钥】💀
💥 打穿过:ASP.NET(2010,可读取 web.config)、Ruby on Rails、 Java 的多个框架、以及大量自制的"加密 cookie"方案。
🔑 教训:这就是第 1 章说的 CCA 攻击的真实形态。 而它的根本解法不是"隐藏错误信息"(太容易漏),而是 —— 先验证 MAC,验证不通过就根本不解密(第 11 章的 Encrypt-then-MAC)。
⚡ 三、CTR:把分组密码变成流密码
Cᵢ = Mᵢ ⊕ E_K(nonce ‖ i)
↑ 计数器递增
⭐ 注意:加密和解密【完全相同】,都是异或
⭐ 注意:E_K 从来没有被"反着用"过 —— 只用了加密方向
| 性质 | CTR |
|---|---|
| 加密/解密 | ✅ 都能完全并行 ⭐ |
| 需要填充 | ❌ 不需要(明文多长密文多长) |
| 随机访问 | ✅ 想解第 n 块直接算 E_K(nonce‖n) |
| 只需要 E_K | ✅ 硬件/代码更小(不用实现解密方向) |
| nonce 要求 | ⭐ 唯一即可,不需要随机 |
| nonce 重复 | 💀 等于密钥复用的 OTP(第 5 章) |
🔑 CTR 是现代的默认选择 —— GCM、ChaCha20-Poly1305 内部都是 CTR 结构。
📊 四、对比表(可以直接拿去用)
| ECB | CBC | CTR | |
|---|---|---|---|
| 安全性 | 💀 绝不使用 | ⚠️ 能用但坑多 | ✅ 好 |
| 并行加密 | ✅ | ❌ | ✅ |
| 并行解密 | ✅ | ✅ | ✅ |
| 需要填充 | ✅ | ✅ | ❌ |
| IV/nonce | 无 | 随机且不可预测 | 唯一即可 |
| 随机访问 | ✅ | 部分 | ✅ |
| 提供完整性 | ❌ | ❌ | ❌ |
⭐ 注意最后一行:三个都不提供完整性。 这就是为什么这三个模式今天都不该单独使用 —— 见下。
✅ 五、那实际该用什么
❌ 不要单独用任何一个上面的模式
✅ 用【认证加密 AEAD】:
├─ AES-GCM ← CTR + GMAC,有硬件加速时最快
├─ ChaCha20-Poly1305 ← 无硬件加速时更快,移动端首选
└─ AES-GCM-SIV ⭐ nonce 重复时"只泄露该消息重复",不会灾难性崩溃
# ✅ 这才是你应该写的代码
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=256)
aead = AESGCM(key)
nonce = os.urandom(12) # ⚠️ 每条消息必须不同
ct = aead.encrypt(nonce, b"transfer 100 CNY", b"account-id-42")
# ↑ AAD:不加密但被认证的数据
pt = aead.decrypt(nonce, ct, b"account-id-42") # 篡改任一部分都会抛异常 ⭐
💡 AAD(附加认证数据)是个好东西: 像消息头、路由信息这类需要明文可见、但不能被篡改的字段,放进 AAD。
⭐ AES-GCM-SIV 值得单独提一句: 普通 GCM 一旦 nonce 重复就是灾难(能恢复认证密钥,伪造任意消息)。 SIV 模式做到"nonce 误用弹性" —— 重复时最多泄露"这两条消息相同", 不会让整个密钥崩溃。如果你无法保证 nonce 唯一,就用它。
🔗 和站内其他章的关系
| 相关的地方 | 和这一章的关系 |
|---|---|
| 第 6 章 分组密码只能处理 128 bit | 工作模式解决"长消息怎么办" |
| 第 6 章 PRP 可当 PRF | CTR 模式的理论依据 |
| 第 5 章 nonce 铁律 | CTR 直接继承 |
| 第 1 章 CCA 攻击 | padding oracle 是它的真实形态 |
| 第 11 章 Encrypt-then-MAC | padding oracle 的根本解法 |
| 大模型 · 合规与伦理 | ⚠️ ECB 那只企鹅在脱敏场景里天天重演:用确定性映射替换 PII,相同的值仍然落到相同的标记,结构原封不动地留在数据里 ⭐ |
| 智能体教程 · 工具设计 | 一个正面冲突的取舍:那边要求错误信息尽量详细("写给模型看的教学材料"),而在这里,详细的错误信息就是 padding oracle ⭐ |
| AI 基础设施 · 流水线并行 | CBC 加密不可并行(Cᵢ 依赖 Cᵢ₋₁)和流水线的「气泡」是同一种顺序依赖,缓解办法也一样:切小块,让它们互相重叠 |
✅ 检查点
- ECB 为什么不安全?用 IND-CPA 的语言说一遍。
- CBC 的公式是什么?它的 IV 有什么要求?和 CTR 的 nonce 要求有什么不同?
- BEAST 攻击利用了什么?
- Padding oracle 攻击的原理是什么?根本解法是什么?
- CTR 模式有哪四个优点?
- 为什么 CTR 模式只需要实现 E_K,不需要 D_K?
- 三种模式都不提供什么?该用什么代替?
- AES-GCM-SIV 解决了什么问题?
👀 答案
- 因为 E_K 是确定性的,相同明文块产生相同密文块,明文结构原样保留(企鹅图)。IND-CPA 语言:攻击者提交 m₀="AAAA...AAAA"(两块相同)和 m₁="AAAA...BBBB"(两块不同),看密文两块是否相同就 100% 分辨。
- Cᵢ = E_K(Mᵢ ⊕ Cᵢ₋₁),C₀ = IV。IV 必须随机且不可预测;CTR 的 nonce 只需要唯一,不需要随机。这是两个不同的要求。
- 利用 TLS 1.0 用上一条消息的最后一个密文块当下一条的 IV,使 IV 可预测,攻击者能构造明文验证他对某明文块的猜测,逐字节破解 cookie。
- 服务器对"填充错误"和其他错误返回不同响应(错误码/时间/日志),攻击者获得解密预言机,每次询问泄露 1 bit,约 256 次解出一字节,不需要密钥就能完整解密。根本解法:Encrypt-then-MAC——先验证 MAC,验证不通过就根本不解密。
- ①加解密都能完全并行 ②不需要填充 ③支持随机访问 ④只需实现加密方向。
- 因为解密是 Mᵢ = Cᵢ ⊕ E_K(nonce‖i)——E_K 从来没被反着用过,加解密都是"生成密钥流再异或"。
- 都不提供完整性。该用 AEAD:AES-GCM、ChaCha20-Poly1305、AES-GCM-SIV。
- nonce 误用弹性。普通 GCM 一旦 nonce 重复是灾难(能恢复认证密钥、伪造任意消息),SIV 重复时最多泄露"这两条消息相同",不会让密钥崩溃。无法保证 nonce 唯一时就用它。
🛑 可以停在这里
⚡ 走神救援
分组密码一次只能加密128bit,长消息靠「工作模式」——选错模式比选错算法后果严重。ECB(每块单独加密)💀绝不使用:E_K 确定性 → 相同明文块产生相同密文块 → 明文结构原样保留(企鹅图);IND-CPA 语言:提交"AAAA|AAAA"和"AAAA|BBBB",看密文两块是否相同就100%分辨。CBC:Cᵢ=E_K(Mᵢ⊕Cᵢ₋₁),⭐IV 必须随机且不可预测(不只是唯一)——💥BEAST 就是因为 TLS1.0 用上条消息的末块当IV使其可预测,逐字节破解cookie;💥Padding Oracle:服务器对"填充错误"返回不同响应 = 送给攻击者一个解密预言机,约256次询问解出一字节,完全不需要密钥(打穿过 ASP.NET/Rails)→ ⭐根本解法不是隐藏错误信息,而是 Encrypt-then-MAC:先验MAC,不过就根本不解密。CTR:Cᵢ=Mᵢ⊕E_K(nonce‖i),⭐加解密完全并行、不需填充、支持随机访问、只需实现加密方向(E_K 从没被反着用过);nonce 只需唯一,但重复=密钥复用的OTP。⭐三种模式都不提供完整性 → 都不该单独使用。✅实际用 AEAD:AES-GCM(有硬件加速最快)、ChaCha20-Poly1305(移动端)、⭐AES-GCM-SIV(nonce 误用弹性——普通GCM的nonce重复会泄露认证密钥导致可伪造任意消息,SIV 最多泄露"两条消息相同")。AAD 放需要明文可见但不能被篡改的字段(消息头、路由信息)。
下一节 👉 08-安全定义.md ⭐⭐