🏠 总目录📚 本教程 11 · MAC 与认证加密
📑 本页目录(点开跳转)

11 · MAC 与认证加密

28 分钟 | ⭐⭐ "加密了"到"安全了"的最后一步


🎯 一句话

第 1 章说过:加密不提供完整性。 这一章补上那一半 —— 而且组合的顺序会决定成败。


✍️ 一、MAC:带密钥的"防伪标签"

   MAC = Message Authentication Code

   发送方:tag = MAC(K, message),把 (message, tag) 一起发
   接收方:重算 MAC(K, message),和收到的 tag 比对

   ⭐ 没有 K 的人无法伪造出合法的 tag

安全定义:UF-CMA(选择消息攻击下的不可伪造性)

挑战者 持有密钥 K 攻击者 没有 K 给我 m₁ 的 tag tag₁ = MAC(K, m₁) ⋮ 任意多次 输出伪造 (m*, t*)
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)$$

   ipad = 0x36 重复,opad = 0x5C 重复

   ⭐ 为什么是【双层嵌套】而不是简单的 H(K‖M):
      → 内层 H 的输出是【固定长度】的
      → 外层再哈希一次,攻击者拿到的输出不能被"接着往下算"
      → 长度扩展攻击失效 ✅(第 10 章)

🔑 HMAC 的一个漂亮性质即使底层哈希的抗碰撞性被打破,HMAC 仍可能是安全的。 实际上 HMAC-MD5 至今没有实用的伪造攻击,尽管 MD5 的碰撞几秒就能造。 (但仍然不要用 —— 没有理由冒这个险。)

② Poly1305 / GMAC —— 基于多项式求值

   把消息看成多项式的系数,在一个有限域上求值:

   tag = (m₁·rⁿ + m₂·rⁿ⁻¹ + ... + mₙ·r) + s   mod p

   ⭐ 极快(比哈希快很多),适合高吞吐
   ⚠️ 但它是【一次性 MAC】:
      同一个 (r, s) 用两次 → 攻击者能解出 r → 完全崩溃 💀
      → 所以必须每条消息用不同的 nonce 派生新的 (r, s)

💥 这就是 AES-GCM 的 nonce 重用为什么是灾难: 不只是泄露明文异或(流密码那部分), 还会让攻击者恢复出 GMAC 的认证密钥 → 之后能伪造任意消息 ⭐ 这比 CTR 模式单独的 nonce 重用严重得多。


⚖️ 三、组合顺序:三选一,只有一个是对的

❌ Encrypt-and-MAC(分别做)

   C = Enc(K₁, M)
   T = MAC(K₂, M)          ← 对【明文】做 MAC
   发送 (C, T)

   💥 问题:MAC 不保证隐藏明文信息!
      MAC 是确定性的 → 相同明文产生相同 tag
      → 攻击者能判断两条密文是否对应同一明文 💀
      → 违反 IND-CPA

用在哪:SSH(历史遗留)

❌ MAC-then-Encrypt(先认证再加密)

   T = MAC(K₂, M)
   C = Enc(K₁, M ‖ T)      ← 把 tag 也加密了
   发送 C

   💥 问题:接收方【必须先解密才能验证】
      → 攻击者能让你解密任意垃圾数据
      → padding oracle 攻击的温床 ⭐(第 7 章)

用在哪:TLS 1.0/1.1 —— 这正是 BEAST、Lucky13 等一系列攻击的根源。

✅ Encrypt-then-MAC(先加密再认证)⭐

   C = Enc(K₁, M)
   T = MAC(K₂, C)          ← 对【密文】做 MAC
   发送 (C, T)

   接收方:
   ① 先验证 T ← 用恒定时间比较
   ② 验证失败 → 【直接拒绝,根本不解密】⭐
   ③ 验证通过 → 才解密

   ✅ 攻击者的解密预言机变成一个只会说"无效"的东西
   ✅ 可证明:IND-CPA 的加密 + UF-CMA 的 MAC ⟹ IND-CCA ⭐

🔑 记住这个顺序,以及两条配套规则: ① 加密密钥和 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(附加认证数据)该放什么

   ✅ 需要明文可见、但不能被篡改的字段:
      消息序号、路由信息、协议版本、时间戳、发送方 ID

   💡 一个实用技巧:把【上下文】放进 AAD,能防重放和串用
      比如把 "session=abc;seq=42" 放 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 或签名

✅ 检查点

  1. MAC 的安全定义 UF-CMA 是什么?用人话说一遍。
  2. MAC 和数字签名的关键区别是什么?
  3. HMAC 为什么要用双层嵌套结构?
  4. Poly1305 为什么是"一次性 MAC"?违反会怎样?
  5. 为什么 AES-GCM 的 nonce 重用比 CTR 的更严重?
  6. 三种组合顺序各是什么?前两种分别错在哪?
  7. Encrypt-then-MAC 的两条配套规则是什么?漏掉 IV 会怎样?
  8. AAD 该放什么?举一个防重放/串用的技巧。
  9. AEAD 能防重放攻击吗?该怎么解决?
👀 答案
  1. 攻击者能任意查询"给我这条消息的 tag",仍然无法为一条没问过的消息造出合法 tag。人话:就算让你看无数条"消息+标签",你也造不出一条新消息的合法标签
  2. MAC 是对称的——验证者也持有密钥,所以他也能伪造不提供不可否认性。数字签名是非对称的,只有私钥持有者能签。
  3. 因为内层 H 的输出是固定长度的,外层再哈希一次让攻击者拿到的输出不能被接着往下算长度扩展攻击失效
  4. 因为它是多项式求值,同一个 (r,s) 用两次,攻击者能解出 r,之后能伪造任意消息。所以必须每条消息用不同 nonce 派生新的 (r,s)。
  5. 因为 CTR 的 nonce 重用只泄露明文异或;GCM 还会让攻击者恢复出 GMAC 的认证密钥,之后能伪造任意消息——从"泄露"升级成"完全失控"。
  6. Encrypt-and-MAC:对明文做 MAC,MAC 是确定性的 → 相同明文相同 tag → 能判断两条密文是否对应同一明文,违反 IND-CPAMAC-then-Encrypt:接收方必须先解密才能验证 → padding oracle 的温床(TLS 1.0/1.1 的 BEAST、Lucky13 根源)③✅Encrypt-then-MAC
  7. 加密密钥和 MAC 密钥必须不同(用 HKDF 从主密钥派生两个)②MAC 必须覆盖所有影响解密的东西,包括 IV/nonce、算法标识、AAD。漏掉 IV:攻击者篡改 IV 就能可控地改变 CBC 第一个明文块,而 MAC 照样通过。
  8. 需要明文可见但不能被篡改的字段:消息序号、路由信息、协议版本、时间戳、发送方 ID。技巧:把上下文(如 "session=abc;seq=42")放进 AAD,攻击者重放到另一个会话就会验证失败。
  9. 不能。AEAD 只保证"这条消息是真的",不保证"是新的"。解法必须在协议层:消息序号(放 AAD,检查单调递增)、时间戳+窗口、挑战-响应。密码学原语保证真实性,协议设计保证新鲜性。

🛑 可以停在这里

走神救援

MAC = 带密钥的防伪标签,安全定义 UF-CMA:看无数条"消息+标签"也造不出新消息的合法标签。⚠️MAC 是对称的,验证者也能伪造 ⟹ 不提供不可否认性(那要用数字签名)。HMAC = H((K⊕opad)‖H((K⊕ipad)‖M)),⭐双层嵌套是为了让内层输出固定长度、外层再哈希一次,使长度扩展失效;漂亮性质:HMAC-MD5 至今没有实用伪造攻击(但仍别用)。Poly1305/GMAC 基于多项式求值,极快但是一次性 MAC——⚠️同一个 (r,s) 用两次攻击者能解出 r,所以💥AES-GCM 的 nonce 重用比 CTR 严重得多:不只泄露明文异或,还会泄露 GMAC 认证密钥 → 之后能伪造任意消息。⭐三种组合顺序只有一个对:❌Encrypt-and-MAC(对明文做MAC,确定性 → 相同明文相同tag → 违反IND-CPA,SSH用的)❌MAC-then-Encrypt(必须先解密才能验证 → padding oracle 温床,TLS1.0/1.1 的 BEAST/Lucky13 根源)✅⭐Encrypt-then-MAC(对密文做MAC,验证不过就根本不解密;可证明 IND-CPA加密 + UF-CMA的MAC ⟹ IND-CCA)。两条配套规则:加密密钥和MAC密钥必须不同(HKDF派生)、MAC必须覆盖 IV/nonce/算法标识/AAD(漏掉IV则攻击者改IV就能改CBC第一块而MAC照过)。✅实际直接用 AEAD:AES-GCM、ChaCha20-Poly1305、AES-GCM-SIV(抗nonce误用)、XChaCha20(192bit nonce 可随机生成)。AAD 放需要明文可见但不可篡改的字段,⭐技巧:把 session/seq 放 AAD 能防重放和串用。⚠️AEAD 防不了重放——它只保证"真"不保证"新",⭐新鲜性必须在协议层做(序号单调递增、时间戳窗口、挑战-响应)。

下一节 👉 12-Merkle树.md

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