本页目录

WireGuard 协议

WireGuard 把 VPN 协议简化到 ~4000 行代码——Noise 协议框架做密钥交换,ChaCha20+Poly1305 做 AEAD 加密,Cryptokey Routing 把公钥和允许的 IP 绑定。没有状态机,没有协商,只有加密。

概述

WireGuard(Jason Donenfeld, 2016)重新定义了 VPN 的简洁性——~4000 行代码对比 OpenVPN(~70,000)和 IPsec(~400,000)。设计哲学是"不协商":加密套件固定(Curve25519 + ChaCha20-Poly1305),端口扫描不可见(不响应未认证的包),连接通过公钥而非 IP 标识(支持透明漫游)。WireGuard 已于 Linux 5.6 合入主线,成为 Linux 上最快的 VPN 实现。Noise IK 握手使用 1-RTT 完成密钥交换,每 120s 自动 rekey 保证前向安全。

设计原则

WireGuard 是一层 3 VPN,设计目标是把密钥交换和加密简化到极致:

  • 无协商: 加密套件固定 (Curve25519 + ChaCha20-Poly1305 + BLAKE2s + SipHash24 + HKDF)。不协商 cipher suite — 没有 downgrade attack。
  • 无连接: 无"连接建立"状态 — 收到有效认证的包就建立 session,不回应未认证包 (端口扫描看不到)。
  • 静默: 不主动发任何包 (除非配置了 PersistentKeepalive — 需要穿透 NAT 时才用)。
  • 漫游: 端点 IP 可变 — 每次收到有效包,更新 peer endpoint。

Noise IK 握手

WireGuard 使用 Noise Protocol Framework 的 Noise_IK 模式 (I=static key for Initiator, K=static key for Responder):

双方预共享:
  I_s: Initiator 的静态私钥 (Curve25519)
  I_pub: Initiator 的静态公钥 (对端知道并配置)
  R_s: Responder 的静态私钥
  R_pub: Responder 的静态公钥 (对端知道并配置)

Initiator → Responder (Handshake Initiation, 148 bytes):
  type = 1 (4 bytes)
  sender_index = (4 bytes, 随机)
  unencrypted_ephemeral (E_i_pub, 32 bytes)   ← DH ephemeral
  encrypted_static (AEAD):
    I_pub (32 bytes)                          ← initiator 身份
    timestamp (12 bytes)                      ← TAI64N, 防止重放
  mac1 (16 bytes): MAC(Responder pubkey, msg)
  mac2 (16 bytes): 如果收到过 cookie reply → cookie value; 否则 zero

Responder → Initiator (Handshake Response, 92 bytes):
  type = 2 (4 bytes)
  sender_index = (4 bytes)
  receiver_index = initiator's sender_index
  unencrypted_ephemeral (E_r_pub, 32 bytes)   ← DH ephemeral
  encrypted_nothing (AEAD): 空 (0 bytes)       ← 但 AEAD tag 验证完整性
  mac1 (16 bytes)
  mac2 (16 bytes): 0

完成握手后,双方各自:
  1. DH(E_i_priv, E_r_pub) = DH(E_i_pub, E_r_priv) = DH(E_r_priv, E_i_pub) = shared
  2. 从 shared + pubkeys → HKDF 派生:
    - 对称密钥 (T_send, T_recv)
    - 每 2^60 个包后换 key (约 280,000 年 at 1Gbps)

密钥旋转 (Rekey)

每次 Handshake Initiation 生成新的 ephemeral DH 密钥对 → 新的 session keys。默认 120 秒无 traffic → 发新的 Handshake Initiation (rekey)。旧 session 的包仍然能被接收 (直到新 session 的 key 投入使用,短的 transition 窗口)。

Timer 状态机

每 peer 有 timer:
  - handshake_initiation_interval: REKEY_TIMEOUT + jitter (默认 120s 后) 
    → 如果上次 handshake 超过 120s → 发新 handshake → 生成新 session key
  - persistent_keepalive_interval: 定期发 keepalive (空消息, 仅 for NAT rebind 检测)
    → 默认 0 (不发送, 除非配置)
  - retry_interval: 指数退避 (5s, 10s, 20s, ...) for handshake retry
  - zero: 如果没有 data 要发送 → 什么都不做 (静默, 不发任何包)

Cookie Reply (防止 DoS): 如果收到疑似伪造的 Handshake Initiation (mac1 验证通过但 mac2 为空),Responder 发送 Cookie Reply 消息。对方必须在后续 Handshake 中包含此 cookie (mac2)。这证明了对端拥有声称的 IP 的可达性。

Data Plane: Transport Data

Transport Data (type=4, 最小 32 bytes):
  receiver_index (4 bytes): 对端的 sender_index → 快速查 peer
  counter (8 bytes): 单调递增, 每个 message +1
  encrypted_encapsulated_packet (AEAD):
    IP packet (IPv4 or IPv6) — 原始包被整个加密
  → AEAD tag (16 bytes) = Poly1305 MAC

/proc/config

# 查看 peer 信息:
wg show

# 添加 peer:
wg set wg0 peer <pubkey> allowed-ips 10.0.0.2/32 endpoint 1.2.3.4:51820

# handshake age:
wg show wg0 latest-handshakes

参考

  • whitepaper: wireguard.com/papers/wireguard.pdf
  • Noise: noiseprotocol.org/noise.html (IK pattern)
  • 源码⁠: drivers/net/wireguard/ (~4000 lines): noise.[ch] (handshake), receive.c, send.c

Keywords: WireGuard, Noise IK, Curve25519, ChaCha20-Poly1305, timer, rekey, roaming, cookie reply