核心特点
WireGuard 是现代 VPN 协议,设计理念是简单、高效、安全:
- 代码量:约 4,000 行(对比 OpenVPN 100,000+ 行)
- 加密套件:固定,无协商(ChaCha20-Poly1305 + Curve25519)
- 性能:内核态实现,延迟 <1ms,吞吐 10Gbps+
- 设计哲学:最小化攻击面,无协商 = 无降级攻击
1. 协议概述
1.1 设计目标
WireGuard 设计目标:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 1. 简单 │
│ ├─ 代码量小,易于审计 │
│ ├─ 配置简单,一个配置文件 │
│ └─ 无复杂状态机 │
│ │
│ 2. 高效 │
│ ├─ 内核态实现 │
│ ├─ 单 UDP 端口 │
│ └─ 零拷贝设计 │
│ │
│ 3. 安全 │
│ ├─ 现代加密算法 │
│ ├─ 固定加密套件(无协商) │
│ ├─ 完美前向保密 │
│ └─ 防重放攻击 │
│ │
│ 4. 无状态 │
│ ├─ 无连接概念 │
│ ├─ 类似 UDP 的无状态设计 │
│ └─ 按需建立隧道 │
│ │
└─────────────────────────────────────────────────────────────────┘
1.2 与其他 VPN 协议对比
| 特性 | WireGuard | IPsec | OpenVPN |
|---|---|---|---|
| 代码量 | ~4,000 行 | ~400,000 行 | ~100,000 行 |
| 加密算法 | 固定(无协商) | 可协商 | 可协商 |
| 实现位置 | 内核态 | 内核态 | 用户态 |
| 协议 | UDP | ESP/UDP | TCP/UDP |
| 握手时间 | ~100ms | 1-3s | 1-5s |
| 连接状态 | 无状态 | 有状态 | 有状态 |
| 配置复杂度 | 简单 | 复杂 | 中等 |
| NAT 穿透 | 原生支持 | 需要额外配置 | 支持 |
2. 加密算法
2.1 加密套件(固定,无协商)
┌─────────────────────────────────────────────────────────────────┐
│ WireGuard 加密套件 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 用途 │ 算法 │ 密钥长度 │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 身份认证/密钥交换 │ Curve25519 (ECDH) │ 256 bits │ │
│ │ 对称加密 │ ChaCha20 │ 256 bits │ │
│ │ 消息认证 │ Poly1305 │ 128 bits │ │
│ │ 哈希函数 │ BLAKE2s │ 256 bits │ │
│ │ 密钥派生 │ HKDF (基于 BLAKE2s) │ - │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 为什么固定不协商? │
│ ├─ 防止降级攻击 │
│ ├─ 简化实现 │
│ ├─ 减少攻击面 │
│ └─ 确保最高安全性 │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 Curve25519(密钥交换)
Curve25519 特点:
1. 椭圆曲线 Diffie-Hellman (ECDH)
├─ 私钥:32 字节随机数
├─ 公钥:32 字节(从私钥派生)
└─ 共享密钥:32 字节(双方各自计算)
2. 计算过程:
Alice Bob
┌──────────────┐ ┌──────────────┐
│ 私钥 a │ │ 私钥 b │
│ 公钥 A = a*G │ │ 公钥 B = b*G │
└───────┬──────┘ └───────┬──────┘
│ │
│ 交换公钥 │
│──────── A ─────────────────▶│
│◀──────────────────── B ─────│
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 共享密钥 │ │ 共享密钥 │
│ S = a*B │ = 相同 = │ S = b*A │
│ = a*(b*G) │ │ = b*(a*G) │
└──────────────┘ └──────────────┘
3. 优势:
├─ 常数时间实现(防时序攻击)
├─ 无需验证(自动处理无效输入)
└─ 32 字节公钥(传输效率高)
2.3 ChaCha20-Poly1305(加密 + 认证)
ChaCha20-Poly1305 是 AEAD(Authenticated Encryption with Associated Data):
┌─────────────────────────────────────────────────────────────────┐
│ ChaCha20-Poly1305 加密 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 输入: │
│ ├─ 明文 (Plaintext) │
│ ├─ 密钥 (256 bits) │
│ ├─ Nonce (96 bits) │
│ └─ AAD (Additional Authenticated Data, 可选) │
│ │
│ 输出: │
│ ├─ 密文 (Ciphertext) │
│ └─ 认证标签 (Auth Tag, 128 bits) │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ChaCha20 Stream Cipher │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 明文 ── XOR ──▶ 密文 │ │ │
│ │ │ ▲ │ │ │
│ │ │ │ │ │ │
│ │ │ Keystream │ │ │
│ │ │ (由密钥 + Nonce 生成) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Poly1305 MAC │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 密文 + AAD ──▶ 128-bit 认证标签 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 最终数据包: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 密文 │ 认证标签 (16 bytes) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 优势: │
│ ├─ 软件实现快(无 AES-NI 依赖) │
│ ├─ 常数时间(安全) │
│ ├─ 同时提供加密和认证 │
│ └─ 128-bit 认证标签(防篡改) │
│ │
└─────────────────────────────────────────────────────────────────┘
2.4 BLAKE2s(哈希函数)
BLAKE2s 特点:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 用途: │
│ ├─ 握手消息哈希 │
│ ├─ 密钥派生 (HKDF) │
│ └─ 链式密钥计算 │
│ │
│ 特点: │
│ ├─ 输出:256 bits (32 bytes) │
│ ├─ 速度:比 SHA-256 快 3-5 倍 │
│ ├─ 安全:与 SHA-3 同级别 │
│ └─ 针对软件实现优化 │
│ │
│ 在 WireGuard 中的使用: │
│ │
│ HASH(data) = BLAKE2s-256(data) │
│ │
│ MAC(key, data) = BLAKE2s-256(key || data) │
│ │
│ KDF(key, input) = HKDF-BLAKE2s(key, input) │
│ │
└─────────────────────────────────────────────────────────────────┘
3. 握手协议
3.1 握手概述
WireGuard 使用 Noise Protocol Framework 的 IK 模式:
特点:
├─ 1-RTT 握手(一个往返)
├─ 双向身份认证
├─ 完美前向保密 (PFS)
├─ 身份隐藏(可选)
└─ 抗重放攻击
密钥层级:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 静态密钥 (长期) │
│ ├─ 静态私钥:保存在配置文件中 │
│ └─ 静态公钥:与对端共享 │
│ │
│ 临时密钥 (短期,每次握手生成) │
│ ├─ 临时私钥:握手后销毁 │
│ └─ 临时公钥:在握手消息中传输 │
│ │
│ 会话密钥 (由握手派生) │
│ ├─ 发送密钥:用于加密发送的数据 │
│ └─ 接收密钥:用于解密接收的数据 │
│ │
└─────────────────────────────────────────────────────────────────┘
3.2 握手流程
┌─────────────────────────────────────────────────────────────────────────┐
│ WireGuard 握手流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Initiator (发起方) Responder (响应方) │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 静态密钥对 │ │ 静态密钥对 │ │
│ │ 私钥: i │ │ 私钥: r │ │
│ │ 公钥: I = i*G │ │ 公钥: R = r*G │ │
│ │ │ │ │ │
│ │ 临时密钥对 (新生成) │ │ │ │
│ │ 私钥: e_i │ │ │ │
│ │ 公钥: E_i = e_i*G │ │ │ │
│ └──────────┬──────────┘ └──────────▲──────────┘ │
│ │ │ │
│ │ Message 1 (Initiation) │ │
│ │ ┌─────────────────────────────────────┐│ │
│ │ │ 类型: 1 ││ │
│ │ │ 发送方索引: 12345 (随机) ││ │
│ │ │ 临时公钥: E_i (32 bytes) ││ │
│ │ │ 加密的静态公钥: Enc(I, ...) ││ │
│ │ │ 时间戳: T (12 bytes) ││ │
│ │ │ MAC1: ... (16 bytes) ││ │
│ │ │ MAC2: ... (16 bytes) ││ │
│ │ └─────────────────────────────────────┘│ │
│ │────────────────────────────────────────▶│ │
│ │ │ │
│ │ ┌────────────────────┤ │
│ │ │ 生成临时密钥对 │ │
│ │ │ 私钥: e_r │ │
│ │ │ 公钥: E_r │ │
│ │ │ │ │
│ │ │ 派生会话密钥: │ │
│ │ │ 使用 E_i, I, R, E_r│ │
│ │ └────────────────────┘ │
│ │ │ │
│ │ Message 2 (Response) │ │
│ │ ┌─────────────────────────────────────┐│ │
│ │ │ 类型: 2 ││ │
│ │ │ 发送方索引: 67890 (随机) ││ │
│ │ │ 接收方索引: 12345 (来自消息1) ││ │
│ │ │ 临时公钥: E_r (32 bytes) ││ │
│ │ │ 加密数据: Enc(..., 会话密钥) ││ │
│ │ │ MAC1: ... (16 bytes) ││ │
│ │ │ MAC2: ... (16 bytes) ││ │
│ │ └─────────────────────────────────────┘│ │
│ │◀────────────────────────────────────────│ │
│ │ │ │
│ ┌──────────┴──────────┐ │ │
│ │ 派生会话密钥: │ │ │
│ │ 使用 E_i, I, R, E_r │ │ │
│ │ 与 Responder 相同 │ │ │
│ └─────────────────────┘ │ │
│ │ │ │
│ │ ========== 握手完成,开始数据传输 ========== │
│ │ │ │
│ │ Data Messages (双向) │ │
│ │ ┌─────────────────────────────────────┐│ │
│ │ │ 类型: 4 ││ │
│ │ │ 接收方索引 ││ │
│ │ │ 计数器 (防重放) ││ │
│ │ │ 加密数据 ││ │
│ │ └─────────────────────────────────────┘│ │
│ │◀═══════════════════════════════════════▶│ │
│ │ 双向加密通信 │ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.3 握手消息格式
┌─────────────────────────────────────────────────────────────────────────┐
│ Message 1: Initiation (148 bytes) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┬─────────┬─────────────────────────────────────────────┐ │
│ │ Offset │ Size │ 字段 │ │
│ ├─────────┼─────────┼─────────────────────────────────────────────┤ │
│ │ 0 │ 1 byte │ 消息类型 (0x01) │ │
│ │ 1 │ 3 bytes │ 保留 (全 0) │ │
│ │ 4 │ 4 bytes │ 发送方索引 (Sender Index, 随机) │ │
│ │ 8 │ 32 bytes│ 临时公钥 E_i (Ephemeral Public Key) │ │
│ │ 40 │ 48 bytes│ 加密的静态公钥 (Encrypted Static PubKey) │ │
│ │ 88 │ 28 bytes│ 加密时间戳或其他数据 │ │
│ │ 116 │ 16 bytes│ MAC1 (对整个消息的认证) │ │
│ │ 132 │ 16 bytes│ MAC2 (可选,用于cookie验证) │ │
│ └─────────┴─────────┴─────────────────────────────────────────────┘ │
│ │
│ 总大小:148 bytes │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Message 2: Response (92 bytes) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┬─────────┬─────────────────────────────────────────────┐ │
│ │ Offset │ Size │ 字段 │ │
│ ├─────────┼─────────┼─────────────────────────────────────────────┤ │
│ │ 0 │ 1 byte │ 消息类型 (0x02) │ │
│ │ 1 │ 3 bytes │ 保留 (全 0) │ │
│ │ 4 │ 4 bytes │ 发送方索引 (Sender Index) │ │
│ │ 8 │ 4 bytes │ 接收方索引 (Receiver Index, 来自消息1) │ │
│ │ 12 │ 32 bytes│ 临时公钥 E_r (Ephemeral Public Key) │ │
│ │ 44 │ 16 bytes│ 加密数据 (Empty, 仅用于密钥确认) │ │
│ │ 60 │ 16 bytes│ MAC1 │ │
│ │ 76 │ 16 bytes│ MAC2 │ │
│ └─────────┴─────────┴─────────────────────────────────────────────┘ │
│ │
│ 总大小:92 bytes │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Message 4: Data (变长) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┬─────────┬─────────────────────────────────────────────┐ │
│ │ Offset │ Size │ 字段 │ │
│ ├─────────┼─────────┼─────────────────────────────────────────────┤ │
│ │ 0 │ 1 byte │ 消息类型 (0x04) │ │
│ │ 1 │ 3 bytes │ 保留 (全 0) │ │
│ │ 4 │ 4 bytes │ 接收方索引 (Receiver Index) │ │
│ │ 8 │ 8 bytes │ 计数器 (Counter, 防重放) │ │
│ │ 16 │ N bytes │ 加密数据 (Encrypted Payload) │ │
│ └─────────┴─────────┴─────────────────────────────────────────────┘ │
│ │
│ 最小大小:32 bytes (16 bytes 头部 + 16 bytes Poly1305 tag) │
│ 最大大小:65535 bytes (UDP 限制) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.4 密钥派生详解
┌─────────────────────────────────────────────────────────────────────────┐
│ 密钥派生过程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 初始状态: │
│ ├─ Initiator: 静态密钥 (i, I), 临时密钥 (e_i, E_i) │
│ ├─ Responder: 静态密钥 (r, R) │
│ └─ Initiator 知道 Responder 的静态公钥 R │
│ │
│ Step 1: Initiator 发送 Message 1 │
│ ───────────────────────────────────── │
│ 计算: │
│ DH1 = DH(e_i, R) = e_i * R # 临时-静态 DH │
│ DH2 = DH(i, R) = i * R # 静态-静态 DH │
│ │
│ 派生中间密钥: │
│ C_i = KDF(DH1, DH2) │
│ │
│ 使用 C_i 加密静态公钥 I 和时间戳 │
│ │
│ │
│ Step 2: Responder 收到 Message 1,发送 Message 2 │
│ ─────────────────────────────────────────────────────────── │
│ 计算: │
│ DH1 = DH(r, E_i) = r * E_i # 对应 e_i * R │
│ DH2 = DH(r, I) = r * I # 对应 i * R │
│ │
│ 生成临时密钥对 (e_r, E_r) │
│ │
│ 计算: │
│ DH3 = DH(e_r, E_i) = e_r * E_i # 临时-临时 DH │
│ DH4 = DH(e_r, I) = e_r * I # 临时-静态 DH │
│ │
│ 派生最终会话密钥: │
│ T_i = KDF(DH1, DH2, DH3, DH4) │
│ │
│ (发送密钥, 接收密钥) = Split(T_i) │
│ │
│ │
│ Step 3: Initiator 收到 Message 2 │
│ ───────────────────────────────────── │
│ 计算: │
│ DH3 = DH(e_i, E_r) = e_i * E_r # 对应 e_r * E_i │
│ DH4 = DH(i, E_r) = i * E_r # 对应 e_r * I │
│ │
│ 派生最终会话密钥: │
│ T_i = KDF(DH1, DH2, DH3, DH4) # 与 Responder 相同 │
│ │
│ (发送密钥, 接收密钥) = Split(T_i) │
│ │
│ │
│ 最终结果: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Initiator Responder │ │
│ │ ├─ 发送密钥: K_send ├─ 发送密钥: K_recv │ │
│ │ └─ 接收密钥: K_recv └─ 接收密钥: K_send │ │
│ │ (互为相反) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4. 数据传输
4.1 数据包结构
┌─────────────────────────────────────────────────────────────────────────┐
│ WireGuard 数据包 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 外层 (UDP/IP): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ IP 头 │ UDP 头 │ WireGuard 载荷 │ │
│ │ 20 B │ 8 B │ 变长 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ WireGuard 载荷 (Message Type 4): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 类型 │ 保留 │ 接收方索引 │ 计数器 │ 加密数据 + Poly1305 Tag │ │
│ │ 1 byte │ 3 B │ 4 bytes │ 8 bytes │ 变长 (最少 16 B) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 加密数据内部 (解密后): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 原始 IP 数据包 │ │
│ │ ├─ IP 头 (20+ bytes) │ │
│ │ ├─ TCP/UDP 头 │ │
│ │ └─ 应用数据 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 完整封装示例: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 外层 IP │ UDP │ WG 头 │ 加密的内层 IP │ TCP │ HTTP 数据 │ Tag │ │
│ │ 20 B │ 8 B │ 16 B │ 20 B │ 20 B│ 变长 │ 16 B │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 总开销:20 + 8 + 16 + 16 = 60 bytes (不含内层 IP) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4.2 防重放机制
┌─────────────────────────────────────────────────────────────────────────┐
│ 防重放机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WireGuard 使用 64 位计数器 + 滑动窗口 │
│ │
│ 计数器: │
│ ├─ 每个数据包有唯一的 64 位计数器 │
│ ├─ 从 0 开始递增 │
│ └─ 计数器不连续的数据包被丢弃 │
│ │
│ 滑动窗口: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 接收窗口 (2048 位位图) │ │
│ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │
│ │ │ 0 │ 0 │ 1 │ 1 │ 0 │ 1 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │...│ │ │
│ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │
│ │ ▲ ▲ │ │
│ │ │ │ │ │
│ │ 最低位 最高位 │ │
│ │ (窗口起点) (窗口终点) │ │
│ │ │ │
│ │ 窗口大小:2048 个包 │ │
│ │ 1 = 已接收, 0 = 未接收/可接收 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 判断逻辑: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 收到计数器 = C 的数据包: │ │
│ │ │ │
│ │ if C > 最高位: │ │
│ │ # 新包,更新窗口 │ │
│ │ 窗口右移 (C - 最高位) 位 │ │
│ │ 标记 C 为已接收 │ │
│ │ 接受数据包 │ │
│ │ │ │
│ │ elif C < 最低位: │ │
│ │ # 太旧,在窗口之外 │ │
│ │ 丢弃数据包 (可能是重放) │ │
│ │ │ │
│ │ else: │ │
│ │ # 在窗口内 │ │
│ │ if 位图[C - 最低位] == 1: │ │
│ │ # 已经接收过 │ │
│ │ 丢弃数据包 (重放攻击) │ │
│ │ else: │ │
│ │ # 新包 │ │
│ │ 标记 C 为已接收 │ │
│ │ 接受数据包 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 64 位计数器容量: │
│ ├─ 2^64 = 1.8 × 10^19 个包 │
│ ├─ 即使每秒 100 万包,可运行 584,000 年 │
│ └─ 实际上永不溢出 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4.3 密钥轮换
┌─────────────────────────────────────────────────────────────────────────┐
│ 密钥轮换机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WireGuard 自动进行密钥轮换,确保完美前向保密 │
│ │
│ 触发条件: │
│ ├─ 时间:2-3 分钟无数据传输后 │
│ ├─ 数据量:发送约 2^64 字节后(实际上不会触发) │
│ └─ 或者:一方主动发起重握手 │
│ │
│ 轮换流程: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 时间线: │ │
│ │ ─────────────────────────────────────────────────────────────│ │
│ │ T=0 T=2min T=4min T=6min │ │
│ │ │ │ │ │ │ │
│ │ ▼ ▼ ▼ ▼ │ │
│ │ 握手1 握手2 握手3 握手4 │ │
│ │ 密钥K1 密钥K2 密钥K3 密钥K4 │ │
│ │ │ │ │ │ │ │
│ │ │ 2分钟 │ 2分钟 │ 2分钟 │ │ │
│ │ └───────────┴─────────────┴─────────────┘ │ │
│ │ │ │
│ │ 密钥独立性: │ │
│ │ ├─ K2 无法从 K1 派生 │ │
│ │ ├─ 知道 K1 不能解密 K2 时期的流量 │ │
│ │ └─ 完美前向保密 (Perfect Forward Secrecy) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 轮换过程(无感知): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 发送方检测到需要重新握手 │ │
│ │ └─ 距上次握手超过 2 分钟 │ │
│ │ │ │
│ │ 2. 发送方发送新的握手消息 (Message 1) │ │
│ │ └─ 生成新的临时密钥对 │ │
│ │ │ │
│ │ 3. 完成新的握手 │ │
│ │ └─ 派生新的会话密钥 │ │
│ │ │ │
│ │ 4. 切换到新密钥 │ │
│ │ ├─ 旧密钥销毁 │ │
│ │ └─ 使用新密钥加密后续数据 │ │
│ │ │ │
│ │ 5. 应用层完全无感知 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
5. Cookie 机制(防 DoS)
5.1 DoS 防护
┌─────────────────────────────────────────────────────────────────────────┐
│ Cookie 机制(防 DoS) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 问题:攻击者可能发送大量伪造的握手消息 │
│ ├─ 消耗 CPU 进行加密计算 │
│ └─ 消耗内存存储会话状态 │
│ │
│ 解决方案:Cookie 验证机制 │
│ ├─ 类似 SYN Cookie │
│ ├─ 在负载高时要求客户端证明其 IP 可达 │
│ └─ 无状态验证 │
│ │
│ 流程: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 正常情况: │ │
│ │ Client ──── Message 1 ────▶ Server │ │
│ │ Client ◀─── Message 2 ───── Server │ │
│ │ (正常响应) │ │
│ │ │ │
│ │ 负载高时: │ │
│ │ Client ──── Message 1 ────▶ Server │ │
│ │ Client ◀─── Cookie Reply ── Server │ │
│ │ (MAC2 = 空,要求 Cookie) │ │
│ │ │ │
│ │ Client ──── Message 1 ────▶ Server │ │
│ │ (带上 MAC2 = Cookie) │ │
│ │ Client ◀─── Message 2 ───── Server │ │
│ │ (验证 Cookie 后正常响应) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Cookie 计算: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Cookie = MAC(服务器密钥, 客户端IP || 时间戳) │ │
│ │ │ │
│ │ 服务器密钥:每 2 分钟轮换 │ │
│ │ 时间戳:2 分钟粒度 │ │
│ │ 结果:Cookie 在 2 分钟内有效 │ │
│ │ │ │
│ │ 验证: │ │
│ │ Cookie' = MAC(当前密钥, 客户端IP || 当前时间) │ │
│ │ Cookie'' = MAC(上一密钥, 客户端IP || 上一时间) // 2分钟重叠 │ │
│ │ if Cookie == Cookie' || Cookie == Cookie'': │ │
│ │ 验证通过 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6. NAT 穿透
6.1 NAT 穿透机制
┌─────────────────────────────────────────────────────────────────────────┐
│ NAT 穿透机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WireGuard 原生支持 NAT 穿透: │
│ ├─ 使用 UDP 协议 │
│ ├─ Keepalive 机制维持 NAT 映射 │
│ └─ 无需 STUN/TURN 等额外协议 │
│ │
│ NAT 类型处理: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Full Cone NAT (最简单) │ │
│ │ └─ 任何外部地址都可以向映射端口发送数据 │ │
│ │ │ │
│ │ 2. Restricted Cone NAT │ │
│ │ └─ 只有内部曾发送过数据的 IP 才能发送 │ │
│ │ │ │
│ │ 3. Port Restricted Cone NAT │ │
│ │ └─ 只有内部曾发送过数据的 IP:Port 才能发送 │ │
│ │ │ │
│ │ 4. Symmetric NAT (最难) │ │
│ │ └─ 每个目标 IP:Port 映射到不同端口 │ │
│ │ └─ 需要双方同时发送数据 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Keepalive 机制: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 默认:每 25 秒发送一个保活包 │ │
│ │ ├─ 维持 NAT 映射有效 │ │
│ │ ├─ 检测连接状态 │ │
│ │ └─ 包大小:一个空的数据包(32 bytes) │ │
│ │ │ │
│ │ 配置:PersistentKeepalive = 25 (秒) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 双向打洞: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 场景:双方都在 NAT 后面 │ │
│ │ │ │
│ │ Peer A (NAT-A) Peer B (NAT-B) │ │
│ │ │ │ │ │
│ │ │──── UDP to B's public ───▶│ (创建 NAT-A 映射) │ │
│ │ │ │ │ │
│ │ │◀─── UDP to A's public ────│ (创建 NAT-B 映射) │ │
│ │ │ │ │ │
│ │ │◀═════════════════════════▶│ │ │
│ │ │ 双向通信建立 │ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7. 性能特性
7.1 性能数据
┌─────────────────────────────────────────────────────────────────────────┐
│ 性能基准 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 测试环境:Intel i7-8700 (6 核), 10 Gbps NIC │
│ │
│ 吞吐量: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 场景 │ 吞吐量 │ CPU 使用率 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 明文 (无加密) │ 9.5 Gbps │ 15% │ │
│ │ WireGuard (ChaCha20) │ 8.8 Gbps │ 35% │ │
│ │ WireGuard (AES-NI) │ 9.2 Gbps │ 25% │ │
│ │ OpenVPN (AES-256-GCM) │ 1.2 Gbps │ 100% (单核) │ │
│ │ IPsec (AES-256-GCM) │ 7.5 Gbps │ 45% │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 延迟: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 场景 │ 延迟增加 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 本地 (同机) │ +0.1 - 0.3 ms │ │
│ │ 同数据中心 │ +0.2 - 0.5 ms │ │
│ │ 跨区域 │ +0.5 - 1.0 ms │ │
│ │ 对比 OpenVPN │ WireGuard 快 5-10× │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 握手时间: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ VPN 协议 │ 握手时间 │ 连接数限制 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ WireGuard │ ~100 ms │ 无限制 │ │
│ │ OpenVPN │ 1-3 s │ 受限于 TLS │ │
│ │ IPsec (IKEv2) │ 500-1000 ms │ 受限于 SA │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.2 内核态实现优势
┌─────────────────────────────────────────────────────────────────────────┐
│ 内核态实现优势 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 对比用户态实现: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 用户态 VPN (如 OpenVPN): │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 网卡 │ ──▶ │ 内核 │ ──▶ │ 用户态 │ │ │
│ │ │ │ │ │ │ VPN程序 │ │ │
│ │ └─────────┘ └─────────┘ └────┬────┘ │ │
│ │ │ │ │
│ │ 拷贝1 拷贝2 ▼ │ │
│ │ ┌─────────┐ │ │
│ │ │ 加密 │ │ │
│ │ └────┬────┘ │ │
│ │ │ │ │
│ │ 拷贝3 拷贝4 ▼ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 网卡 │ ◀── │ 内核 │ ◀── │ 用户态 │ │ │
│ │ │ (发送) │ │ │ │ VPN程序 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ 总计:4 次数据拷贝 + 4 次上下文切换 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ WireGuard (内核态): │ │
│ │ ┌─────────┐ ┌─────────────────────────────────────────┐ │ │
│ │ │ 网卡 │ ──▶ │ 内核 │ │ │
│ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ └─────────┘ │ │ WireGuard 模块 │ │ │ │
│ │ │ │ - 直接访问 skb │ │ │ │
│ │ │ │ - 内核态加密 │ │ │ │
│ │ │ │ - 零拷贝 │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 总计:0 次数据拷贝 + 0 次上下文切换(相对于网络栈) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
8. 安全分析
8.1 安全特性
┌─────────────────────────────────────────────────────────────────────────┐
│ 安全特性总结 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ 完美前向保密 (Perfect Forward Secrecy) │
│ └─ 定期重新握手,每次生成新的会话密钥 │
│ └─ 即使长期私钥泄露,历史流量仍安全 │
│ │
│ ✅ 身份隐藏 │
│ └─ 静态公钥在握手时加密传输 │
│ └─ 被动攻击者无法识别参与者 │
│ │
│ ✅ 防重放攻击 │
│ └─ 64 位计数器 + 2048 包滑动窗口 │
│ └─ 重放的数据包被丢弃 │
│ │
│ ✅ 防降级攻击 │
│ └─ 加密套件固定,无协商 │
│ └─ 无法强制使用弱算法 │
│ │
│ ✅ 常数时间实现 │
│ └─ 所有加密操作在常数时间内完成 │
│ └─ 防止时序攻击 │
│ │
│ ✅ 防 DoS │
│ └─ Cookie 机制 │
│ └─ 负载高时要求 IP 验证 │
│ │
│ ✅ 密钥隔离 │
│ └─ 发送和接收使用不同的密钥 │
│ └─ 密钥独立,互不影响 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
8.2 已知限制
┌─────────────────────────────────────────────────────────────────────────┐
│ 已知限制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 后量子安全 │
│ ├─ 当前:Curve25519 不抗量子计算 │
│ ├─ 影响:量子计算机可破解密钥交换 │
│ └─ 未来:可能需要迁移到后量子算法 │
│ │
│ 2. 元数据泄露 │
│ ├─ 攻击者可以看到: │
│ │ ├─ 源/目的 IP 地址 │
│ │ ├─ 时间和流量模式 │
│ │ └─ 数据包大小 │
│ └─ 无法看到:加密内容 │
│ │
│ 3. 静态对端配置 │
│ ├─ 需要预先知道对端的公钥 │
│ └─ 不支持动态发现(需要额外的配置管理) │
│ │
│ 4. 无内置用户认证 │
│ ├─ 只验证密钥,不验证用户身份 │
│ └─ 需要额外的身份管理层(如 SPIFFE) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9. 配置示例
9.1 基本配置
# /etc/wireguard/wg0.conf
[Interface]
# 本地配置
PrivateKey = ABCD... # 32 字节 base64 编码私钥
Address = 10.0.0.1/24 # VPN 内部 IP
ListenPort = 51871 # UDP 监听端口
DNS = 1.1.1.1 # 可选:DNS 服务器
# MTU 设置(WireGuard 开销 60 bytes)
# MTU = 1420 # 默认 1500 - 60 = 1440,保守设为 1420
[Peer]
# 对端配置
PublicKey = EFGH... # 对端 32 字节 base64 编码公钥
Endpoint = 203.0.113.2:51871 # 对端公网 IP:Port
AllowedIPs = 10.0.0.2/32, 192.168.1.0/24 # 允许通过隧道的 IP
# 可选配置
PersistentKeepalive = 25 # 保活间隔(秒),用于 NAT 穿透9.2 Cilium 配置
# Cilium 使用 WireGuard
apiVersion: v1
kind: ConfigMap
metadata:
name: cilium-config
namespace: kube-system
data:
encryption: "wireguard"
encryption-node-network: "true"10. 总结
10.1 WireGuard 的核心优势
┌─────────────────────────────────────────────────────────────────────────┐
│ WireGuard 核心优势 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 简单性 │
│ ├─ 代码量小(4,000 行) │
│ ├─ 配置简单 │
│ └─ 易于审计和验证 │
│ │
│ 2. 高性能 │
│ ├─ 内核态实现 │
│ ├─ 低延迟(<1ms) │
│ └─ 高吞吐(10Gbps+) │
│ │
│ 3. 现代加密 │
│ ├─ ChaCha20-Poly1305 │
│ ├─ Curve25519 │
│ └─ BLAKE2s │
│ │
│ 4. 安全设计 │
│ ├─ 完美前向保密 │
│ ├─ 防重放攻击 │
│ ├─ 防降级攻击 │
│ └─ 身份隐藏 │
│ │
│ 5. 网络友好 │
│ ├─ NAT 穿透 │
│ ├─ 漫游支持 │
│ └─ 无连接状态 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
10.2 适用场景
| 场景 | 推荐 WireGuard |
|---|---|
| 站点到站点 VPN | ✅ |
| 远程访问 VPN | ✅ |
| 云间互联 | ✅ |
| 容器网络加密 (Cilium) | ✅ |
| 移动设备 VPN | ✅ |
| 需要复杂认证 | ⚠️ 需要额外层 |
10.3 一句话总结
WireGuard 是一个简单、高效、安全的现代 VPN 协议,使用固定的现代加密套件(ChaCha20-Poly1305 + Curve25519),内核态实现,提供完美前向保密和防重放攻击,适合各种 VPN 场景。