密码学 37:算法正确为什么仍会失败——随机数复用、侧信道与验证边界
团队使用成熟密码库,单元测试里的签名和加密都验过,却把 AEAD 的同一个 key/nonce 用来加密两份订单;或者在 ECDSA 签名时复用了签名随机标量。这不是“数学算法被破解”,而是调用者破坏了算法要求的输入条件。回想 05 的四类值:密码 salt、AEAD nonce、签名 nonce、业务防重放序号不共享一个“随机数就行”的规则。
复用为何具体危险
ECDSA 对特定消息摘要 z 与随机标量 k 的一种标准算式是 s = k⁻¹(z + r·d) mod n:d 是签名私钥,r 来自同一临时曲线点。如果同一私钥对不同 z 复用相同 k,两份签名用相同的 r,且差值可逆时,攻击者从公开的 (r,s1,z1)、(r,s2,z2) 解出 k=(z1-z2)/(s1-s2) mod n,进而求 d=(s1·k-z1)/r mod n。恢复的是私钥本身,不是“签名可以重播”这么弱的结果。ECDSA 实际实现还要核对曲线、nonce 生成、低 s 变体和消息预处理,不能拿手算的玩具公式替真正的曲线库。
AES-GCM 的安全要求同一个密钥的 nonce 不重用。GCM 使用计数器流保护明文,同 key/nonce 的两段密文主体使用相同的加密流:攻击者算 C1 xor C2 = P1 xor P2。若一份订单的格式或内容已知,就可能推出另一份中与其对齐的秘密内容;认证安全保证也可能受损。AAD 保证未篡改不等于 nonce 复用后仍安全:用原样 tag 仍验真,也无法把已经暴露的明文关系收回。
1 | |
examples/cryptography/37_misuse.py 将签名公式置于小素数模的教学算术:n=101, d=19, k=13, r=7,展示两份合成摘要及签名标量如何反推 d。这里 r 没有通过椭圆曲线点乘获得,输出绝不能说成“真实 ECDSA 私钥被程序攻破”。另一实验用成熟 cryptography AESGCM 真原语和一次性密钥,故意两次使用同一 12 字节 nonce:实际密文 XOR 等于明文 XOR;用错误 AAD 解密则 tag 报错。
先按仓库内 examples/cryptography/README.md 完成环境准备;以下命令从仓库根目录执行。
1 | |
测试 2 项通过只能证明所选输入下这两个特定失误与失败对照。要避免真实错误,签名遵循所用算法的经审查 nonce 生成或确定性规范,GCM 按所用方案保障每把密钥下 nonce 的唯一性;分布式多实例与重启要把生成策略和状态持久化策略一并设计。恒时比较、验证顺序、错误输出与限流也是需要独立审查的边界:HMAC 可以用 compare_digest 避免普通早停比较的一类时序泄漏,但不能据此证明整个进程无侧信道;少量计时样本更不能成为“无泄漏证明”。
贯穿案例里,TLS 的记录密钥与序号由实现管理,不要手写 TLS 来“控制 nonce”;钱包的交易签名使用成熟钱包及算法规定的 nonce;RPC 应用要持久化业务消费状态。三者各自可能出错,互相通过不会替另一层补漏。一个签名原语验真,只说明那些字节在对应公钥下有效,交易是否合法、是否包含和订单是否履行仍要按 28–35 分别验。
练习及答案
画图题: 把 AEAD nonce、ECDSA 签名 k 和订单 API nonce 各贴到真实密钥持有人与验证者一侧。如果订单 API 无消费状态但使用了正确 AES-GCM,攻击者能怎样重放?
答案: AEAD nonce 与加密密钥由通信/存储实现管理;签名 k 与私钥由钱包签名器内部生成,公开签名供对端用公钥核对;API nonce 是请求与服务端消费表之间的业务协议字段。正确加密保证传输中未改字节,但服务端若每次仅重新验签、不检查同一请求身份及 nonce 是否已消费,录到的合法请求仍可能被重复执行。
实验变更题: 把签名模型的 s_second 强行设成 s_first,为何反解失败?将真实 AES-GCM 第二次加密改用不同 nonce,ciphertext xor = plaintext xor 的断言还应成立吗?
答案: 两式 s1-s2=0 在模 101 下没有逆元,不能按该公式求出 k;不能靠让代码除零就证明复用 k 安全,实际特定相同签名/消息须另分析。换用不同 nonce 后流不同,原先的 XOR 恒等关系通常不再成立;没有精确匹配不等于一次有限实验已证明生产系统 nonce 从不会冲突。





