有人抓到两个使用 Noise Framework 的系统,宣称“它们都是 Noise,握手双方必然相互认证、能抵抗同样的重放和身份泄露”。不一定。Noise 描述多种不同握手模式;什么静态公钥事先已知,什么身份在握手中发送,谁先能确认谁,以及之后的数据包格式,必须按具体模式和应用实现核对。WireGuard 使用相关的密码学构件和 Noise 体系的握手思路,并不是“任何 Noise 模式加一个 UDP socket”就成了 WireGuard。

从 KK 这一种模式推导身份

本章只选 Noise_KK_25519_ChaChaPoly_BLAKE2b:K 表示两端都在握手开始前已获得并信任对方静态公钥;25519 表示临时和静态密钥交换使用的曲线,ChaChaPoly 对规定的握手 payload 做认证加密,BLAKE2b 参与哈希和派生。双方交换临时公钥,混入静态/临时 DH 得出的秘密,随握手消息处理不断更新握手 hash/密钥状态。两条消息之后 split 得到两方向不同的传输 cipher state;客户端的发送状态与服务端的接收状态对应,反向同理。

1
2
3
4
5
6
7
已固定身份:客户端保存服务静态公钥 S_s;服务保存客户端静态公钥 S_c

客户端 KK:临时 e_c ──► e_c + 按模式计算/认证的第一条消息
服务端 KK:临时 e_s ◄── e_s + 按模式计算/认证的第二条消息
双方按固定消息模式混合临时/静态 DH、握手 hash

split:client.tx == server.rx;client.rx == server.tx

实验采用发布的 noise-protocol@3.0.2 的限定实现而不是自己写曲线或 AEAD。examples/cryptography/E03_noise_handshake.mjs 为本次测试生成两端临时静态密钥,完成真实 KK 双消息和 split,检查方向对应;在客户端将预配的服务静态公钥换为第三方公钥而服务端仍用原私钥时,握手认证失败。这项负例说明“静态公钥必须有可信分发起点”,不是说系统可以“信任它刚从 UDP 发来的任意公钥”。

先按仓库内 examples/cryptography/README.md 完成环境准备;以下命令从仓库根目录执行。

1
2
node examples/cryptography/E03_noise_handshake.mjs
node --test examples/cryptography/E03_noise_handshake.test.mjs

真握手和错静态身份 1 项测试通过;使用库限定 25519/ChaChaPoly/BLAKE2b,并及时销毁两个握手对象。没有创建 WireGuard 接口、执行真正的 IK/PSK 握手、发送任何 WireGuard 数据包或观测它的密钥更新、防重放窗口;这些 NOT_RUN。成功 split 也不证明应用传输了订单、不证明用户身份映射为某个静态密钥、不会自动覆盖重启后的密钥更新策略。若需要比较其它 Noise 模式,先把各模式 pre-message 的公钥信任来源和前几条消息的加密/认证状态逐一列出,不能照抄此 KK 结论。

删除预置服务公钥检查时,攻击者可把客户端配置引向自己的公钥和私钥;算法仍会完成一条对攻击者认证的握手,TLS 或 SSH 的公钥绑定困惑在这里以另一种配置形态重现。对于钱包经 HTTPS RPC 的结课场景,本章提供的是对“可信公钥从哪里来”的迁移练习;没有理由因此建议把浏览器 HTTPS 替换成自制 Noise 协议。

练习及答案

画图题: 在握手图上标注客户端/服务器事先各持有哪把私钥、公钥,哪一把是临时生成的。若把模式换成 NN,仍可按 KK 的口径称“两端已知对方静态身份”吗?

答案: 本地静态私钥只在各自端点;各自可信配置里有对方静态公钥;交换的是本次临时公钥,DH 的秘密不在网络上原样发送。NN 没有 KK 所需的两端预置静态身份,不可据 KK 的检查结果声称 NN 同样认证了已知的服务器名称和客户端身份。

实验变更题: 保留服务端原静态私钥,只让客户端对 noise.initialize('KK', true,...) 传入第三方公钥,结果怎样?如果双方同时把配置改为第三方自己的静态密钥并完成握手,原 orders.test 预期身份是否自动被认证?

答案: 原服务私钥与被固定的错误公钥不对应,消息认证不能成功。若两端真的都按攻击者的密钥配置好,握手可能对那套密钥合法;密码学检查不含“这个公钥属于 orders.test”的事实,需由独立可信配置与业务身份映射提供。

资料与导航