订单内容经加密后谁也看不懂,攻击者却把原本属于 POST /orders 的密文交给另一个接口,或者把标记身份的请求上下文换成了另一个用户。如果接收端只解密、不核对这些可见的上下文与被保护的消息是否同属一笔操作,就可能处理错误对象。第 06 篇展示 ECB 会泄漏重复结构,教学 XOR 也说明不带校验的密文可能被改。这篇直接用成熟库试一个提供保密性与完整性检查的接口:AEAD。

本章不实现 TLS。它执行真实 AES-GCM 原语的固定向量以及正确解密、改密文、改标签、改上下文和故意重复 nonce 的失败对照。它证明的是所选库、所选输入下的行为,不能凭一个原语实验宣布整个 HTTPS 订单服务安全。

五类输入,三类输出与一条失败路径

设服务和客户端已安全约定一个对称密钥 K,并在这一把密钥的生命周期里不重复使用加密 nonce。调用者将确切的订单字节作为明文,把服务方法、路径和已认定用户等不需加密、却不能被错配的字节作为 AAD(附加认证数据)。这里使用 AES-GCM;得到密文和认证标签。接收者传入相同 K、nonce、AAD 与标签解密,任何不一致都应失败,不能把 update() 吐出的暂存明文提前交给订单业务处理。

1
2
3
4
5
6
7
8
9
K(双方秘密)、nonce(每密钥加密唯一)
+-- 明文 b"order=demo-001;qty=1"
+-- AAD b"POST /orders;user=demo-user"(公开但要绑定)
|
v
AES-GCM 加密 -> 密文 + tag
|
同一 K、nonce、AAD --+--> 校验 tag 后才交出明文
改密文 / 改 AAD / 改 tag -----> 拒绝,不处理订单

AAD 并非“隐形加密区”:在传输中可能是明文,而且用户身份必须由上游已验证的身份来源确定。假如攻击者能同时任选 AAD 中的 user 并让服务端无条件相信它,把这个字符串纳入认证计算也不能凭空给它建立真实身份。AAD 负责把数据与服务自身确定的用途绑定,不能代替登录、证书校验、服务授权或请求重放状态。

nonce 也通常是公开的,但它不是一个随便传、随便重复的装饰字段。AES-GCM 对同密钥 nonce 复用很敏感;RFC 5116 §5.1.1 警告复用会伤害机密性与认证保证。本次的 nonce 与密钥都使用公开全 0 向量以验证算法输出,只用于隔离测试。真实服务必须另行设计按密钥作用域保证唯一的机制,并处理多实例、重启和轮换。这里没有提供生产密钥或部署配置。

正常输入与三种错误输入

用一个固定合成订单加密后,原样拿回密文、标签、nonce、AAD 可恢复订单。再分别试三种独立的变化:翻转密文第一字节、把 AAD 的 POST 改成 GET 或把 demo-user 换成 other-user、翻转标签字节。测试都要求解密抛异常,而不是返回一个“看着还像 JSON”的值;错误密钥同样拒绝。

这里保护了什么?窃听者不应直接从密文恢复原文,修改参与认证的字节不能被当作成功的解密结果。这里没有保护什么?如果一个已经有权的客户端用新的 nonce、正确密钥与合法身份再次加密相同订单,AEAD 会把它当成另一条可验证的消息;订单是否已经处理,要查持久业务状态。若实际 TLS 在代理终止,浏览器与代理之间的一条受保护连接也不能凭这一段原语结果推出代理与后端之间是否加密或授权。

复用相同 key/nonce 究竟泄露什么

为了让 05 的“别重复”不只是口诀,实验故意拿同一公开密钥、同一 12 字节 nonce分别加密 qty=1 与 qty=9 的订单。AES-GCM 的加密部分在该条件下重用同一段密钥流;把两个等长前缀的密文字节逐位 XOR,得到的结果与两个明文字节前缀逐位 XOR 一样。攻击者若知道其中一份明文,还可能据此获取另一份对应位置的内容。这是对保密性质的实际负例,不是“本实验已实际造出能通过标签校验的伪造订单”:认证风险要按规范与更专门测试论证,不把一次 XOR 比较冒充已经完成标签伪造。

1
2
3
同 K、同 nonce: C1 = P1 XOR 同一密钥流
C2 = P2 XOR 同一密钥流
C1 XOR C2 = P1 XOR P2

这些等式描述 GCM 采用的计数器式加密分量在误用条件下的泄漏,而不是 GCM 的完整认证算法。正常每密钥唯一 nonce 是设计前提,不能因为本地测试 encrypt() 连续运行没报错,就以为库会替应用阻止复用。换一个新的 nonce,加密流不同,这条直接 XOR 推导就不再成立;真实 nonce 分配工程仍要单独验收。

哪些输出确实跑过

examples/cryptography/07_aead.mjs 用 Node.js 22.23.2 的内置 node:crypto,其 OpenSSL 后端版本记录为 3.5.7。Python 环境当时没有 cryptography 模块与 pip,因此没有把不可运行的 Python 示例写成“已经通过”。程序只调用成熟 createCipheriv / createDecipheriv,指定 16 字节标签,不重写 AES 或 GCM;解密将明文留在局部缓冲区,只有 decipher.final() 成功后才返回。

1
2
node examples/cryptography/07_aead.mjs
node --test examples/cryptography/07_aead.test.mjs

2026-10-06 UTC 两条命令退出码均为 0,4 项测试通过。公开的全 0 AES-128-GCM 向量给出密文 0388dace60b6a392f328c2b971b2fe78、标签 ab6e47d42cec13bdf53a67b21257bddf;本次运行与向量一致。正确输入还原为真,改密文、改 AAD、改 tag 的拒绝断言为真;测试另覆盖错误用户上下文与错误密钥。故意重复 nonce 的明文 XOR 泄漏断言也为真。这些字段和密钥都是公开测试数据,任何人都不应把它们用于实际服务。

环境与证据列在 writing-plans/cryptography/research/07.md;版本表保留本机二进制未与上游源码构建产物 SHA 比对的缺口。结果属于“真实密码学原语与公开测试向量”,不是“真实协议/组件”,TLS 握手、记录处理和浏览器订单端点仍未运行。

两道带答案的练习

画图题。 网关已经验证用户为 demo-user,业务消息不想公开 qty,但需要让代理知道发往 /orders。哪些字节适合放明文、哪些适合放 AAD?把认证身份的来源写在图上。如果 user 字符串纯由未经验证的客户端任意填写,AEAD 能修复身份问题吗?

可核对答案: qty 等需保密内容放明文输入,让 AEAD 生成密文;方法与路径如需路由可作为可见但受认证的 AAD。验证出的身份可进入服务确定的 AAD 或其他受绑定上下文;如果直接相信攻击者自填的 user,加密器只证明它认证了这串字节,不证明这串字节代表真实用户。网关认证与解密端的信任边界必须画出来。

变更题。 在 07_aead.mjs 中让第二份订单用另一个 12 字节 nonce,再运行测试及脚本。哪个断言应该首先失败?只把它改成 false 的新预期,就能说已经验证了安全分配 nonce 吗?

可核对答案: 之前重复 key/nonce 的明文 XOR 等式不应再成立,repeated_nonce_exposes_plaintext_xor 对应断言失败;如果只改动脚本而没有改测试,测试自己构造的相同 nonce 对照仍会通过。单次换值只能证明这组输入不再出现那个直接 XOR 等式,无法证明全生命周期、跨重启或跨实例的 nonce 唯一性。

资料与导航