📑 本页目录(点开跳转)
11 · MAC 与认证加密
⏱ 28 分钟 | ⭐ "加密了"到"安全了"的最后一步
🎯 一句话
第 1 章说过:加密不提供完整性。 这一章补上那一半 —— 而且组合的顺序会决定成败。
✍️ 一、MAC:带密钥的"防伪标签"
对照
MAC = Message Authentication Code
发送方:tag = MAC(K, message),把 (message, tag) 一起发
接收方:重算 MAC(K, message),和收到的 tag 比对
⭐ 没有 K 的人无法伪造出合法的 tag
安全定义:UF-CMA(选择消息攻击下的不可伪造性)
⭐ 攻击者赢,当且仅当 m* 是【没问过的】消息,且 t* 合法。
💡 人话:就算让你看无数条"消息+标签",你也造不出一条新消息的合法标签。
⚠️ 注意 MAC 和数字签名的区别: MAC 是对称的 —— 验证者也持有 K,所以他也能伪造 ⟹ 不提供不可否认性。 需要不可否认就得用数字签名(第 17 章)。
🔧 二、两种主流 MAC
① HMAC —— 用哈希造 MAC
$$\text{HMAC}_K(M) = H\big((K\oplus \text{opad}) \,\|\, H((K \oplus \text{ipad}) \,\|\, M)\big)$$
结果对照
🔑 HMAC 的一个漂亮性质: 即使底层哈希的抗碰撞性被打破,HMAC 仍可能是安全的。 实际上 HMAC-MD5 至今没有实用的伪造攻击,尽管 MD5 的碰撞几秒就能造。 (但仍然不要用 —— 没有理由冒这个险。)
② Poly1305 / GMAC —— 基于多项式求值
因果链
💥 这就是 AES-GCM 的 nonce 重用为什么是灾难: 不只是泄露明文异或(流密码那部分), 还会让攻击者恢复出 GMAC 的认证密钥 → 之后能伪造任意消息 ⭐ 这比 CTR 模式单独的 nonce 重用严重得多。
⚖️ 三、组合顺序:三选一,只有一个是对的
❌ Encrypt-and-MAC(分别做)
信息关系
用在哪:SSH(历史遗留)
❌ MAC-then-Encrypt(先认证再加密)
关键信息
用在哪:TLS 1.0/1.1 —— 这正是 BEAST、Lucky13 等一系列攻击的根源。
✅ Encrypt-then-MAC(先加密再认证)
流程图
🔑 记住这个顺序,以及两条配套规则: ① 加密密钥和 MAC 密钥必须不同(从主密钥用 HKDF 派生出两个) ② MAC 必须覆盖所有影响解密的东西 —— 包括 IV/nonce、算法标识、AAD
💥 漏掉 IV 的后果:攻击者篡改 IV,CBC 的第一个明文块就被可控地改变, 而 MAC 检查照样通过。
📦 四、AEAD:直接用打包好的
别自己拼装 —— 用现成的认证加密:
| 算法 | 内部结构 | 什么时候用 |
|---|---|---|
| AES-GCM | CTR + GMAC | 有 AES-NI 硬件指令时最快 ⭐ |
| ChaCha20-Poly1305 | ChaCha20 + Poly1305 | 无硬件加速时更快,移动端首选 |
| AES-GCM-SIV | 抗 nonce 误用 | 无法保证 nonce 唯一时 ⭐ |
| AES-CCM | CTR + CBC-MAC | 嵌入式(代码体积小) |
| XChaCha20-Poly1305 | 192-bit nonce | 想随机生成 nonce 又不想操心碰撞 ⭐ |
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import os
key = ChaCha20Poly1305.generate_key()
aead = ChaCha20Poly1305(key)
nonce = os.urandom(12) # ⚠️ 每条消息必须不同
ct = aead.encrypt(nonce, b"transfer 100", b"from=alice;to=bob")
# ↑ AAD:明文可见但被认证
# 篡改密文、AAD 或 nonce 中的任何一个都会抛 InvalidTag ⭐
pt = aead.decrypt(nonce, ct, b"from=alice;to=bob")
AAD(附加认证数据)该放什么:
结果对照
⚠️ 五、AEAD 也解决不了的:重放攻击
关键信息
- 攻击者【原样重发】一条合法消息 —— 密文没被改,MAC 也对
- ⭐ AEAD 无法阻止这个,因为它只保证"这条消息是真的",
- 不保证"这条消息是新的"
- ✅ 解法(必须在协议层做):
- 消息序号(放 AAD 里,接收方检查单调递增)⭐
- 时间戳 + 有效期窗口
- 挑战-响应(服务器发随机数,客户端必须带回来)
💡 这是一个常被忽略的边界: 密码学原语保证"真实性",协议设计保证"新鲜性"。 两件事。
🔗 和站内其他章的关系
| 相关的地方 | 和这一章的关系 |
|---|---|
| 第 1 章 加密不提供完整性 | 本章就是补那一半 ⭐ |
| 第 7 章 padding oracle | MAC-then-Encrypt 的后果 |
| 第 8 章 IND-CCA | Encrypt-then-MAC 达到它 ⭐ |
| 第 10 章 长度扩展 | HMAC 的双层结构就是为它设计的 |
| 第 5 章 nonce 铁律 | GCM 下后果更严重(认证密钥泄露) |
| 第 17 章 数字签名 | MAC 的非对称版,提供不可否认性 |
| Claude 资料库 · 触达生产系统 | 本章末尾那个 AEAD 也挡不住的重放攻击,工程解法就在那里:幂等键 + 时间窗——它属于协议层,不属于密码层 ⭐ |
| 模型上线之后 · 版本回溯与可复现 | ⚠️ 「数据快照带哈希」是无密钥的完整性:只防手滑,不防对手——有对手时哈希会被连数据一起改掉,必须换成 MAC 或签名 |
✅ 检查点
- MAC 的安全定义 UF-CMA 是什么?用人话说一遍。
- MAC 和数字签名的关键区别是什么?
- HMAC 为什么要用双层嵌套结构?
- Poly1305 为什么是"一次性 MAC"?违反会怎样?
- 为什么 AES-GCM 的 nonce 重用比 CTR 的更严重?
- 三种组合顺序各是什么?前两种分别错在哪?
- Encrypt-then-MAC 的两条配套规则是什么?漏掉 IV 会怎样?
- AAD 该放什么?举一个防重放/串用的技巧。
- AEAD 能防重放攻击吗?该怎么解决?
👀 答案
- 攻击者能任意查询"给我这条消息的 tag",仍然无法为一条没问过的消息造出合法 tag。人话:就算让你看无数条"消息+标签",你也造不出一条新消息的合法标签。
- MAC 是对称的——验证者也持有密钥,所以他也能伪造 ⟹ 不提供不可否认性。数字签名是非对称的,只有私钥持有者能签。
- 因为内层 H 的输出是固定长度的,外层再哈希一次让攻击者拿到的输出不能被接着往下算 → 长度扩展攻击失效。
- 因为它是多项式求值,同一个 (r,s) 用两次,攻击者能解出 r,之后能伪造任意消息。所以必须每条消息用不同 nonce 派生新的 (r,s)。
- 因为 CTR 的 nonce 重用只泄露明文异或;GCM 还会让攻击者恢复出 GMAC 的认证密钥,之后能伪造任意消息——从"泄露"升级成"完全失控"。
- ①Encrypt-and-MAC:对明文做 MAC,MAC 是确定性的 → 相同明文相同 tag → 能判断两条密文是否对应同一明文,违反 IND-CPA ②MAC-then-Encrypt:接收方必须先解密才能验证 → padding oracle 的温床(TLS 1.0/1.1 的 BEAST、Lucky13 根源)③✅Encrypt-then-MAC。
- ①加密密钥和 MAC 密钥必须不同(用 HKDF 从主密钥派生两个)②MAC 必须覆盖所有影响解密的东西,包括 IV/nonce、算法标识、AAD。漏掉 IV:攻击者篡改 IV 就能可控地改变 CBC 第一个明文块,而 MAC 照样通过。
- 放需要明文可见但不能被篡改的字段:消息序号、路由信息、协议版本、时间戳、发送方 ID。技巧:把上下文(如 "session=abc;seq=42")放进 AAD,攻击者重放到另一个会话就会验证失败。
- 不能。AEAD 只保证"这条消息是真的",不保证"是新的"。解法必须在协议层:消息序号(放 AAD,检查单调递增)、时间戳+窗口、挑战-响应。密码学原语保证真实性,协议设计保证新鲜性。
🛑 可以停在这里
⚡ 走神救援
MAC 是带密钥的防伪标签,安全定义是「看过无数条消息加标签,也造不出一条新消息的合法标签」。⚠️ 它是对称的——验证者也能伪造,所以它不提供不可否认性(那要用签名)。
HMAC 双层嵌套的理由值得记:⭐ 内层把输入压成固定长度,外层再哈希一次,于是长度扩展攻击失效。
💥⭐ Poly1305 这类基于多项式的 MAC 是一次性的——⚠️ 同一个密钥参数用两次,攻击者就能把它解出来。⭐ 所以 AES-GCM 的 nonce 重用比纯加密模式严重得多:它不只泄露明文的异或,还会泄露认证密钥——之后能伪造任意消息。
⭐⭐ 三种组合顺序只有一个对: ❌ 先各干各的(对明文做 MAC)——确定性标签会让相同明文暴露出来,直接违反语义安全。 ❌ 先 MAC 再加密——⚠️ 必须先解密才能验证,这正是填充预言攻击的温床(历史上那几个著名 TLS 漏洞的根源)。 ✅ ⭐ 先加密再对密文做 MAC——验证不过就根本不解密,而且可证明能把语义安全升级到抗选择密文攻击。
两条配套规则:加密密钥和 MAC 密钥必须不同(派生出来);⭐ MAC 必须覆盖 IV、算法标识和附加数据——⚠️ 漏掉 IV,攻击者改 IV 就能改掉第一块明文,而 MAC 照样通过。
✅ 实际直接用 AEAD 就好,其中有抗 nonce 误用的变体,也有把 nonce 加长到可以随机生成的变体。⭐ 附加数据的一个实用技巧:把会话号和序号放进去,能同时防重放和防串用。
⚠️⭐ 最后一条最容易忘:AEAD 防不了重放——它只保证「真」,不保证「新」。新鲜性必须在协议层做。
下一节 👉 12-Merkle树.md