密码学 01:窃听、篡改、冒充与重放怎样分开
浏览器向订单接口发 quantity=1。攻击者如果能看见消息,已经造成泄露;如果能把数量改成 9,是另一种失败;如果直接构造一条“来自用户 A”的新消息,前两个问题都不解释它;如果原封不动重发旧的有效订单,还会产生第四种失败。“消息安全”不是一个布尔值:必须先说明攻击者站在哪里、能做什么,接着逐项说出想阻止什么。
上一篇用摘要说明“当前消息和当前摘要相等”不能凭空证明来源。这篇继续使用同一合成订单,把四种攻击放在一个不受保护的教学通道上。它没有加密或签名:实验的作用是构造要阻止的失败,不是伪装成已经部署了安全协议。
给攻击者划出能力边界
在这个实验里,浏览器生成 JSON 字节;攻击者控制浏览器与教学接收者之间的一段明文通道。接收者相信 JSON 里的 sender 字段,但没有核对任何身份凭据。攻击者可读取字节、改动内容、插入新消息或记录后再发送。每种能力带来不同的问题。
1 | |
| 攻击 | 失败的具体判据 | 仅阻断这一类攻击所需检查 | 仍没解决什么 |
|---|---|---|---|
| 窃听 | 未授权者能读到订单字段 | 通道上的保密性:对手观察密文不能直接读明文 | 若接收端被攻破,保密通道不再遮住端点内数据 |
| 篡改 | 接收者把 quantity=9 当成原来的 1 |
让受保护字节改动可被发现,并拒绝错误消息 | “没有被改”还不代表发送者有权下单 |
| 冒充 | 对手构造 sender=demo-user 而接收者信以为真 |
把消息与正确的认证身份绑定 | 即使身份正确,仍可能发送两次 |
| 重放 | 一份之前合法的订单产生两次效果 | 把可用次数/时段/序号绑定到业务消费状态 | 去重本身无法认出首次消息是不是伪造 |
其中,保密性关心谁能读;完整性关心受保护内容能否被悄然改变;来源认证关心发送者和可信凭据的关系;新鲜性关心这份数据在此刻是否仍可用。它们可在一个协议里共同实现,也可以分别失效。完整性通常需要可信的鉴别依据,不能理解成“自己算一遍 SHA-256 然后自己比较”。
同一条消息,如何产生四种不同失败
明文是 {"order_id":"demo-001","quantity":1,"sender":"demo-user","sku":"book"},接收者先按 JSON 解码。窃听者甚至不用改动字节:只要它能读到,订单字段就已经泄漏。如果改成数量 9,接收者按照改后的值工作;若仅加密消息而不校验内容是否遭到修改,也不应声称已防篡改。下一篇讨论确定的字节输入,06–07 再讨论加密与认证加密,避免在这里把“密文看不懂”误说成“改不动”。
冒充并不要求夺取浏览器。攻击者构造新 JSON,把 sender 字段填成别人的用户名;若接收者把这个字段当认证结果,第一次提交就可能生效。把攻击者自填的公钥附在消息里、再用它验证攻击者自己生成的签名,同样没解决“公钥代表谁”的问题。TLS 中服务端的身份绑定、公链交易中签名对应的支出条件,是不同验证规则;具体信任起点到 13–14 与 26–28 才逐一核对。
重放则故意不改消息。攻击者记录一次真正有效的订单并再次送达,验签、MAC 或通道保护是否仍通过,要看被验证的对象和会话边界;从“密码学鉴别成功”无法推出“此前没有被业务消费”。即使采用 TLS 1.3 正常 1-RTT 流量保护,合法用户在两个连接里自行提交同一订单,仍可能让应用做两次工作。TLS 1.3 的 0-RTT 是另一种更直接的跨连接重放风险,不能与正常 1-RTT 混为一谈。
这里的典型修补是把身份与操作绑定,并让服务端在可信的持久状态中原子地检查并消费业务唯一值。有效期帮助限制旧请求的可用窗口,却不能替代“一次已用”的检查;只在一台机器的内存 set 中记 ID,重启或切到另一台实例就可能丢状态。即便有全局去重,如果攻击者能抢先提交一个伪造的首次消息,这份去重状态也不能补救身份认证缺失。
最小实验:开放字节与一次性消费
在仓库根目录运行:
1 | |
2026-10-06 UTC,在 Python 3.12.3 上运行上述命令,退出码均为 0,5 个测试通过。实验输出可直接读到原订单的明文字段,改动后解码数量为 9;没有凭据的 sender 字段可以被填成 other-user。一个完全无状态的接收函数先后两次返回 demo-001,表示它无法区分重复提交。SeenOrders 第一次返回 true,同 ID 再次返回 false;但面对冒充者第一次使用这个 ID,它仍返回 true。四个结果各自解释一个缺口,不能把 SeenOrders 的布尔返回值称为真实鉴别或安全证明。
这套代码没有 TLS 端点、消息认证、并发事务、故障恢复或多实例同步,也没有评估攻击者篡改唯一 ID 的能力。在真实系统里,要先确定受保护字节涵盖什么、凭据属于谁,再安排可靠的去重存储。HTTP 消息签名的 RFC 9421 §7.2.2 专门指出:签名覆盖范围不足,或已有签名可以附在另一请求上,都会放大重放风险;仅验证签名值不足以完成业务判定。
拿图里的两条贯穿路径再核对一次:浏览器发 HTTPS 订单时,TLS 负责与正确端点建立受保护的通信,而业务身份、是否允许再次下单仍是应用检查;钱包经 HTTPS RPC 发送交易时,HTTPS 管节点连接,交易签名管链上授权,而链还要检查其自己的 nonce/未花费条件与执行、历史接受规则。两条路径都不能凭一个“验证成功”的提示跨越全部边界。
两道带答案的练习
画图题。 一位攻击者不能读写 HTTPS 记录,但已经拿到了用户有效的应用会话凭据,重新提交相同订单。把 TLS 端点、应用身份检查和业务去重分别标在图上;订单是否可能重复?
可核对答案: TLS 保护客户端与实际终止 TLS 的端点,不替应用判断凭据是否被窃取;持有有效凭据的攻击者可用自己的连接发送合法记录。应用身份检查可能通过,若无原子消费的订单 ID 或等价业务约束,仍可能出现第二次订单效果。代理终止处至后端另看一段保护与授权。
变更题。 运行脚本后,删去 SeenOrders.accept 中 self.order_ids.add(order_id),再跑单元测试。哪个负例会改变?为什么另一个测试仍表明去重无法识别冒充?
可核对答案: 同一 ID 的第二次提交会从 false 变为 true,test_one_consumer_rejects_reused_identifier 失败;test_local_deduplication_does_not_authenticate_sender 的冒充者仍能在首次提交时被接受——它根本没有检查 sender 是否来自有权身份。修复去重代码不等于增加来源认证。
不要试图用“锁住订单”概括这四种性质:锁的类比说不出可信身份从哪里来,也说不出旧订单是否已被处理。碰到协议时先画攻击者能力,再圈输入字节、秘密持有者、可信验证起点和可消费状态;剩下的术语到各原语章节再展开。
资料与导航
- RFC 9846,TLS 1.3,第 2、8 节(2026-07):正常数据、0-RTT 与重放边界;没有以旧 RFC 8446 冒称现行标准。
- RFC 9421,HTTP Message Signatures,§7.2.2:签名覆盖、nonce 与时间窗口的重放风险;本篇没有执行 HTTP 签名协议。
- 00 从 HTTPS 与交易两张图开始 · 02 密码学处理哪些字节。






