土法炼钢兴趣小组的算法知识备份

【WireGuard】协议原理:Noise IKpsk2、四次 DH 与 Cookie

文章导航

分类入口
networksecurity
标签入口
#wireguard#noise-ikpsk2#curve25519#chacha20-poly1305#blake2s#handshake#cookie

目录

设计哲学把「固定原语 + 预知对端公钥」钉死之后,协议层要回答三件事:握手消息里有什么、每个 DH 贡献什么安全属性、DoS 下如何既不分配未认证状态又能证明 IP 归属。本文以官方 Protocol & Cryptography 与 Donenfeld NDSS 2017 §V 为主来源;内核常量取自 Linux 6.6 drivers/net/wireguard/messages.h

更细的源码级握手状态机见 内部实现文 第二节;Noise 记号谱系见 安全信道文

一、固定原语与构造名

WireGuard 使用的构造名(官方 protocol 页):

Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s
符号 含义
DH(priv, pub) Curve25519 标量乘,32 字节
AEAD(key, counter, pt, ad) ChaCha20-Poly1305(RFC 7539);nonce = 32 bit 零 || 64-bit LE counter
HASH / HMAC / MAC BLAKE2s 及其 keyed 变体
TAI64N() 12 字节时间戳(防 initiation 重放)
CONSTRUCTION UTF-8:Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s
IDENTIFIER UTF-8:WireGuard v1 zx2c4 Jason@zx2c4.com

未配置 PSK 时,PSK 视为 32 字节全零,算法路径不变。

消息类型(Linux 6.6 messages.h):

类型
1 Handshake Initiation
2 Handshake Response
3 Cookie Reply
4 Transport Data

二、Noise IK 在 VPN 场景里的含义

Noise IK 模式(简化记号):

IK(s, rs):
  <- s
  ...
  -> e, es, s, ss
  <- e, ee, se

这对 VPN 的运维假设是匹配的:你不会对「完全陌生的公钥」自动建隧道;peer 集合是配置的一部分。

三、四次 DH:各自换来什么

握手过程(双方合计)涉及:

运算 计算 主要贡献
es \(DH(e_i, s_r)\) 用响应方静态密钥「打开」对发起方静态公钥的加密;被动观察者看不到 \(s_i\)
ss \(DH(s_i, s_r)\) 静态–静态:双方长期身份互证的关键材料
ee \(DH(e_i, e_r)\) 临时–临时:前向保密的主来源(临时私钥握手后清零)
se \(DH(s_i, e_r)\) 绑定发起方静态与响应方临时;配合整体模式抵抗若干 KCI 场景

论文与官方文档宣称的性质包括:AKE 安全目标下的认证密钥交换、重放抵抗、perfect forward secrecy、静态公钥身份隐藏(类似 SIGMA 思路)、以及对 KCI 的设计考虑。这些是协议声称 + 后续分析文献的对象;计算模型下的精确定理见 深度篇 对 Dowling & Paterson 的讨论。

sequenceDiagram
  participant I as Initiator
  participant R as Responder
  I->>R: Msg1 Initiation type=1
  Note over I,R: e_i cleartext; s_i and TAI64N encrypted
  R->>I: Msg2 Response type=2
  Note over I,R: e_r cleartext; empty AEAD; keys derived
  I->>R: Msg4 first transport
  Note over R: key confirmation; R may send data

四、消息布局(与官方页对齐)

4.1 Initiation(type = 1)

字段顺序:

  1. message_type / reserved_zero[3] / sender_index
  2. unencrypted_ephemeral[32] —— \(E^{pub}_i\)
  3. encrypted_static —— AEAD 保护的 \(S^{pub}_i\)
  4. encrypted_timestamp —— AEAD 保护的 TAI64N
  5. mac1[16] / mac2[16]

密钥调度要点(官方 protocol 页伪代码):以 HASH(CONSTRUCTION) 为 chaining key 起点,混入 IDENTIFIER\(S^{pub}_r\),再混入临时公钥与 esss 的 DH 输出,逐步更新 hash 与 AEAD key。响应方按相同公式逆向运算,使双方 chaining key / hash 一致。

sender_index 是本端为这次握手/会话分配的 32-bit 索引(类似 SPI),供对端在后续包里引用。

4.2 Response(type = 2)

字段:sender_indexreceiver_index(回指发起方 index)、unencrypted_ephemeral\(E^{pub}_r\))、对空载荷的 AEAD、mac1/mac2

响应方在此混入 eese 与 PSK,再派生传输密钥。官方页写明:response 比 initiation 更短,以降低反射放大动机。

4.3 传输密钥派生

两条握手消息完成后,双方由 chaining key 经 HKDF 风格展开得到:

随后清零临时私钥与中间 chaining 状态(官方页;内核 noise.c 实现同一纪律)。计数器从 0 起,每个方向独立。

4.4 Transport Data(type = 4)

type | reserved | receiver_index | counter | AEAD(packet || zero-pad)

内层 IP 包在加密前按 16 字节倍数零填充(不改 IP 头长度字段),增加流量分析难度;填充不得把 UDP 撑过 MTU。头部长 16 字节,便于原地解密对齐——论文 §V-D6 强调这是为硬件与常见 CPU 友好。

重放:64-bit 计数器只增不减;因 UDP 乱序,接收端用滑动窗口(论文指向 RFC 2401 附录 C / RFC 6479 一类算法)。Linux 6.6 中窗口相关常量见 messages.hCOUNTER_BITS_TOTAL = 8192

五、为何是 1.5 RTT,不是「握手完就能双向发」

Noise IK 两条消息后,双方已经能算出会话密钥。但 WireGuard / Noise 要求:发起方先发第一条 transport,响应方收到后才用新会话发数据

原因(官方 protocol 页 “Connection-less Protocol” 段;NDSS §V 开篇):

因此时间线是:

时刻 谁可以安全发新会话数据
Initiation 发出
Response 返回 发起方可以发
首条 transport 到达响应方 响应方可以发

Dowling & Paterson(ACNS 2018)指出:正是「confirmation 密文用会话密钥加密」,使得把密钥交换单独拿出来做 session-key indistinguishability 证明变得别扭——详见深度篇。工程上这是特性;证明模块化上这是摩擦。

6.1 未认证则尽量沉默

设计目标(NDSS §V-A):认证前不分配状态、对未认证包不响应,降低扫描可见性与状态耗尽面。

6.2 Initiation 时间戳

第一条消息必须可认证,带来 initiation 重放风险:攻击者重放旧 initiation,诱使响应方更新临时态,干扰合法会话。WireGuard 在加密字段里放 TAI64N;响应方对每个 peer 记录最大时间戳,拒绝 \(\le\) 历史最大值的包。响应方重启丢失该状态可接受:当时没有活跃会话可被破坏(论文 §V-A)。

Curve25519 虽快,在洪水下仍可能被 CPU 耗尽。负载高时,响应方可对「MAC1 合法但 MAC2 无效/缺失」的握手返回 Cookie Reply(type = 3),而不是做完整握手。

相对 DTLS / IKEv2 cookie,论文 §V-C 强调三点修补:

  1. 始终要求 MAC1(密钥材料含响应方公钥等),避免对完全陌生扫描者回包,尽量保持「沉默」。
  2. Cookie 本身经 AEAD 保护,降低明文 cookie 被中间人盗用。
  3. AEAD 的 AD 绑定引发 cookie 的那次 MAC1,降低对发起方注入假 cookie 的效果。

Cookie 值为对发起方源 IP 的 MAC,密钥为响应方每两分钟轮换的 secret(COOKIE_SECRET_MAX_AGE = 2 * 60 秒,Linux 6.6 messages.h)。发起方用 cookie 计算 MAC2 后重发。

七、定时器与密钥寿命(实现常量)

官方 protocol 页描述了一组定时行为;Linux 6.6 messages.h 中的秒级常量包括:

常量 含义(摘要)
REKEY_AFTER_TIME 120 s 发起方侧会话老化后触发再握手
REJECT_AFTER_TIME 180 s 超此寿命的密钥拒收
KEEPALIVE_TIMEOUT 10 s 空包 keepalive / 断线探测相关
REKEY_TIMEOUT 5 s 握手重试间隔(加抖动)
REKEY_AFTER_MESSAGES \(2^{60}\) 单密钥消息计数阈值

精确触发条件以官方 protocol 页条目与 timers.c 为准;运维层面记住两句即可:约两分钟量级轮换会话密钥长时间单向静默会靠 keepalive / 再握手维持或拆除

八、小结

下篇把这些消息对应到 Linux 6.6 的 wg_xmitnoise.c 与加解密 worker。

参考资料

规范 / 官方

源码

论文

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-19 · network / security

WireGuard 深度系列:从设计哲学到内核实现

从 Donenfeld NDSS 2017 的设计哲学出发,拆解 Noise IKpsk2、内核数据路径、运维实践与形式化验证争论——把 WireGuard 从「好用的 VPN」讲成可核对的协议与系统。

2026-06-20 · network

【网络工程】WireGuard 内部实现:Cryptokey Routing、Noise IK 握手与内核数据路径

WireGuard 用不到 4000 行内核代码替代了 IPSec/IKE 数十万行的协议栈——这不是少写功能,而是用固定密码学原语、固定握手模式和 cryptokey routing 这三个设计决定,换掉了证书体系、协商机和 SPD/SAD 分离。本文钻进源码和协议握手过程,逐层拆解 cryptokey routing 的双向执行、Noise IKpsk2 的 1.5 RTT 握手(含四次 ECDH 的安全属性贡献)、内核多核队列架构、timer 状态机、cookie DoS 防御和三密钥对轮换。


By .