wireguard实现原理分析
https://github1s.com/WireGuard/wireguard-linux/blob/stable/drivers/net/wireguard/noise.c https://www.wireguard.com/repositories/ https://git.zx2c4.com/wireguard-linux/tree/drivers/net/wireguard/
它有几个特点,一是p2p,二是udp转发,三是完全内核态实现/基于tun网卡实现,四是基于ecc加密。 总的来说,握手包特征比较明显,后续的udp的的特征也比较明显,可以识别出是wg的包。
握手流程是这样的:
握手时序图
仅一次 ECDH 密钥派生 + 两次包交互,握手完成后数据加解密直接复用会话密钥,无额外开销。
https://github.com/WireGuard/wireguard-linux/blob/stable/drivers/net/wireguard/noise.c https://github.com/WireGuard/wireguard-linux/blob/stable/drivers/net/wireguard/noise.h
sequenceDiagram
participant A as Alice (发起方)
participant B as Bob (响应方)
Note over A,B: 阶段0:全局初始化 + 静态密钥配置
A->>A: 1. 初始化 Blake2s 哈希上下文<br/>wg_noise_init() → 生成 handshake_init_hash/chaining_key
A->>A: 2. 设置本地静态私钥并生成公钥<br/>wg_noise_set_static_identity_private_key(&static, private_key)
A->>A: 3. 初始化握手结构并预计算静态-静态 ECDH<br/>wg_noise_handshake_init(&handshake, &static, peer_public, psk, peer)
B->>B: 1. 同Alice:wg_noise_init() 初始化 Blake2s
B->>B: 2. 同Alice:wg_noise_set_static_identity_private_key 设置静态密钥
B->>B: 3. 同Alice:wg_noise_handshake_init 初始化握手结构
Note over A,B: 阶段1:Alice 发起 Type 1 握手包(Initiation)
A->>A: 1. 等待随机数源就绪<br/>wait_for_random_bytes()
A->>A: 2. 生成临时密钥对并构建 Type 1 包<br/>wg_noise_handshake_create_initiation(dst, &handshake)
A->>B: 发送 Type 1 包(UDP)<br/>包结构:魔数(4) + 类型(1) + 临时公钥(32) + 加密静态(48) + 加密时间戳(24) + 发送者索引(4)
Note over A,B: 阶段2:Bob 处理 Type 1 并回复 Type 2(Response)
B->>B: 1. 解析 Type 1 包并查找对等体<br/>wg_noise_handshake_consume_initiation(src, wg)
B->>B: 2. 验证时间戳防重放、验证速率防洪水攻击
B->>B: 3. 生成临时密钥对并构建 Type 2 包<br/>wg_noise_handshake_create_response(dst, &handshake)
B->>A: 发送 Type 2 包(UDP)<br/>包结构:魔数(4) + 类型(2) + 临时公钥(32) + 加密空数据(16) + 接收者索引(4) + 发送者索引(4)
Note over A,B: 阶段3:Alice 处理 Type 2 并建立会话
A->>A: 1. 解析 Type 2 包并验证<br/>wg_noise_handshake_consume_response(src, wg)
A->>A: 2. 派生双向会话密钥并开始会话<br/>wg_noise_handshake_begin_session(&handshake, &keypairs)
A->>B: 双向加密数据传输<br/>发送:使用 keypairs->>sending 密钥加密<br/>接收:使用 keypairs->>receiving 密钥解密并验证
B->>A: 双向加密数据传输
1. 阶段0:wg_noise_init()
// 全局 Blake2s 哈希初始化(WireGuard 模块加载时执行)
static const u8 handshake_name[37] __nonstring = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
static const u8 identifier_name[34] __nonstring = "WireGuard v1 zx2c4 Jason@zx2c4.com";
static u8 handshake_init_hash[NOISE_HASH_LEN] __ro_after_init;
static u8 handshake_init_chaining_key[NOISE_HASH_LEN] __ro_after_init;
void __init wg_noise_init(void)
{
struct blake2s_state blake;
// 1. 生成初始 chaining key:基于 Noise 协议名称
blake2s(handshake_init_chaining_key, handshake_name, NULL,
NOISE_HASH_LEN, sizeof(handshake_name), 0);
// 2. 生成初始 hash:混合 chaining key + WireGuard 标识符
blake2s_init(&blake, NOISE_HASH_LEN);
blake2s_update(&blake, handshake_init_chaining_key, NOISE_HASH_LEN);
blake2s_update(&blake, identifier_name, sizeof(identifier_name));
blake2s_final(&blake, handshake_init_hash);
}
初始化 Noise 协议的哈希上下文,为后续密钥派生提供初始熵;
2. 阶段0:wg_noise_set_static_identity_private_key()
// 设置静态私钥并生成公钥(P2P 身份核心)
void wg_noise_set_static_identity_private_key(
struct noise_static_identity *static_identity,
const u8 private_key[NOISE_PUBLIC_KEY_LEN])
{
// 1. 复制私钥
memcpy(static_identity->static_private, private_key, NOISE_PUBLIC_KEY_LEN);
// 2. Curve25519 私钥钳制(符合规范)
curve25519_clamp_secret(static_identity->static_private);
// 3. 私钥生成公钥
static_identity->has_identity = curve25519_generate_public(
static_identity->static_public, private_key);
}
将用户配置的静态私钥转换为公钥,作为 P2P 对等体的唯一身份标识;
3. 阶段0:wg_noise_handshake_init()
// 初始化握手结构并预计算静态-静态 ECDH
void wg_noise_handshake_init(struct noise_handshake *handshake,
struct noise_static_identity *static_identity,
const u8 peer_public_key[NOISE_PUBLIC_KEY_LEN],
const u8 peer_preshared_key[NOISE_SYMMETRIC_KEY_LEN],
struct wg_peer *peer)
{
// 1. 清零握手结构
memset(handshake, 0, sizeof(*handshake));
// 2. 初始化读写锁
init_rwsem(&handshake->lock);
// 3. 设置类型和对等体指针
handshake->entry.type = INDEX_HASHTABLE_HANDSHAKE;
handshake->entry.peer = peer;
// 4. 复制对端静态公钥
memcpy(handshake->remote_static, peer_public_key, NOISE_PUBLIC_KEY_LEN);
// 5. 复制预共享密钥(可选)
if (peer_preshared_key)
memcpy(handshake->preshared_key, peer_preshared_key, NOISE_SYMMETRIC_KEY_LEN);
// 6. 设置静态身份指针
handshake->static_identity = static_identity;
// 7. 初始状态:ZEROED
handshake->state = HANDSHAKE_ZEROED;
// 8. 预计算静态-静态 ECDH(性能优化)
wg_noise_precompute_static_static(peer);
}
初始化握手状态,预计算静态-静态 ECDH 以提升握手速度。
4. 阶段1:wg_noise_handshake_create_initiation()
// 创建 Type 1 握手包(Alice 发给 Bob)
bool wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst,
struct noise_handshake *handshake)
{
u8 timestamp[NOISE_TIMESTAMP_LEN];
u8 key[NOISE_SYMMETRIC_KEY_LEN];
bool ret = false;
// 1. 等待随机数源就绪(Curve25519 需要)
wait_for_random_bytes();
// 2. 加锁
down_read(&handshake->static_identity->lock);
down_write(&handshake->lock);
// 3. 检查静态身份是否有效
if (unlikely(!handshake->static_identity->has_identity))
goto out;
// 4. 设置包类型
dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION);
// 5. 初始化 chaining key 和 hash
handshake_init(handshake->chaining_key, handshake->hash, handshake->remote_static);
// 6. 生成临时密钥对(e)
curve25519_generate_secret(handshake->ephemeral_private);
if (!curve25519_generate_public(dst->unencrypted_ephemeral, handshake->ephemeral_private))
goto out;
message_ephemeral(dst->unencrypted_ephemeral, dst->unencrypted_ephemeral,
handshake->chaining_key, handshake->hash);
// 7. ECDH:临时私钥 + 对端静态公钥(es)
if (!mix_dh(handshake->chaining_key, key, handshake->ephemeral_private,
handshake->remote_static))
goto out;
// 8. 加密本地静态公钥(s)
message_encrypt(dst->encrypted_static, handshake->static_identity->static_public,
NOISE_PUBLIC_KEY_LEN, key, handshake->hash);
// 9. ECDH:静态私钥 + 对端静态公钥(ss,预计算)
if (!mix_precomputed_dh(handshake->chaining_key, key,
handshake->precomputed_static_static))
goto out;
// 10. 生成并加密时间戳({t})
tai64n_now(timestamp);
message_encrypt(dst->encrypted_timestamp, timestamp, NOISE_TIMESTAMP_LEN,
key, handshake->hash);
// 11. 插入索引哈希表
dst->sender_index = wg_index_hashtable_insert(
handshake->entry.peer->device->index_hashtable, &handshake->entry);
// 12. 更新状态
handshake->state = HANDSHAKE_CREATED_INITIATION;
ret = true;
out:
up_write(&handshake->lock);
up_read(&handshake->static_identity->lock);
memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
return ret;
}
按照 Noise IKpsk2 协议构建 Type 1 握手包,包含临时公钥、加密的静态公钥和时间戳。
5. 阶段2:wg_noise_handshake_consume_initiation()
// 处理 Type 1 握手包(Bob 接收 Alice 的包)
struct wg_peer *wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src,
struct wg_device *wg)
{
struct wg_peer *peer = NULL, *ret_peer = NULL;
struct noise_handshake *handshake;
bool replay_attack, flood_attack;
u8 key[NOISE_SYMMETRIC_KEY_LEN];
u8 chaining_key[NOISE_HASH_LEN];
u8 hash[NOISE_HASH_LEN];
u8 s[NOISE_PUBLIC_KEY_LEN];
u8 e[NOISE_PUBLIC_KEY_LEN];
u8 t[NOISE_TIMESTAMP_LEN];
u64 initiation_consumption;
// 1. 加锁读取静态身份
down_read(&wg->static_identity.lock);
if (unlikely(!wg->static_identity.has_identity))
goto out;
// 2. 初始化 chaining key 和 hash
handshake_init(chaining_key, hash, wg->static_identity.static_public);
// 3. 处理临时公钥(e)
message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash);
// 4. ECDH:静态私钥 + 对端临时公钥(es)
if (!mix_dh(chaining_key, key, wg->static_identity.static_private, e))
goto out;
// 5. 解密对端静态公钥(s)
if (!message_decrypt(s, src->encrypted_static, sizeof(src->encrypted_static),
key, hash))
goto out;
// 6. 查找对等体(通过静态公钥)
peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, s);
if (!peer)
goto out;
handshake = &peer->handshake;
// 7. ECDH:静态私钥 + 对端静态公钥(ss,预计算)
if (!mix_precomputed_dh(chaining_key, key, handshake->precomputed_static_static))
goto out;
// 8. 解密时间戳({t})
if (!message_decrypt(t, src->encrypted_timestamp, sizeof(src->encrypted_timestamp),
key, hash))
goto out;
// 9. 检查重放攻击和洪水攻击
down_read(&handshake->lock);
replay_attack = memcmp(t, handshake->latest_timestamp, NOISE_TIMESTAMP_LEN) <= 0;
flood_attack = (s64)handshake->last_initiation_consumption + NSEC_PER_SEC / INITIATIONS_PER_SECOND > (s64)ktime_get_coarse_boottime_ns();
up_read(&handshake->lock);
if (replay_attack || flood_attack)
goto out;
// 10. 成功!复制所有信息到对等体
down_write(&handshake->lock);
memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN);
if (memcmp(t, handshake->latest_timestamp, NOISE_TIMESTAMP_LEN) > 0)
memcpy(handshake->latest_timestamp, t, NOISE_TIMESTAMP_LEN);
memcpy(handshake->hash, hash, NOISE_HASH_LEN);
memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN);
handshake->remote_index = src->sender_index;
initiation_consumption = ktime_get_coarse_boottime_ns();
if ((s64)(handshake->last_initiation_consumption - initiation_consumption) < 0)
handshake->last_initiation_consumption = initiation_consumption;
handshake->state = HANDSHAKE_CONSUMED_INITIATION;
up_write(&handshake->lock);
ret_peer = peer;
out:
memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
memzero_explicit(hash, NOISE_HASH_LEN);
memzero_explicit(chaining_key, NOISE_HASH_LEN);
up_read(&wg->static_identity.lock);
if (!ret_peer)
wg_peer_put(peer);
return ret_peer;
}
解析 Type 1 包,验证身份,防重放/洪水攻击,更新握手状态。
6. 阶段2:wg_noise_handshake_create_response()
// 创建 Type 2 握手包(Bob 发给 Alice)
bool wg_noise_handshake_create_response(struct message_handshake_response *dst,
struct noise_handshake *handshake)
{
u8 key[NOISE_SYMMETRIC_KEY_LEN];
bool ret = false;
// 1. 等待随机数源就绪
wait_for_random_bytes();
// 2. 加锁
down_read(&handshake->static_identity->lock);
down_write(&handshake->lock);
// 3. 检查状态是否正确
if (handshake->state != HANDSHAKE_CONSUMED_INITIATION)
goto out;
// 4. 设置包类型和接收者索引
dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE);
dst->receiver_index = handshake->remote_index;
// 5. 生成临时密钥对(e)
curve25519_generate_secret(handshake->ephemeral_private);
if (!curve25519_generate_public(dst->unencrypted_ephemeral, handshake->ephemeral_private))
goto out;
message_ephemeral(dst->unencrypted_ephemeral, dst->unencrypted_ephemeral,
handshake->chaining_key, handshake->hash);
// 6. ECDH:临时私钥 + 对端临时公钥(ee)
if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private,
handshake->remote_ephemeral))
goto out;
// 7. ECDH:临时私钥 + 对端静态公钥(se)
if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private,
handshake->remote_static))
goto out;
// 8. 混合预共享密钥(psk)
mix_psk(handshake->chaining_key, handshake->hash, key, handshake->preshared_key);
// 9. 加密空数据({})
message_encrypt(dst->encrypted_nothing, NULL, 0, key, handshake->hash);
// 10. 插入索引哈希表
dst->sender_index = wg_index_hashtable_insert(
handshake->entry.peer->device->index_hashtable, &handshake->entry);
// 11. 更新状态
handshake->state = HANDSHAKE_CREATED_RESPONSE;
ret = true;
out:
up_write(&handshake->lock);
up_read(&handshake->static_identity->lock);
memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
return ret;
}
- 核心作用:按照 Noise IKpsk2 协议构建 Type 2 握手包,包含临时公钥和加密的空数据。
7. 阶段3:wg_noise_handshake_consume_response()
// 处理 Type 2 握手包(Alice 接收 Bob 的包)
struct wg_peer *wg_noise_handshake_consume_response(struct message_handshake_response *src,
struct wg_device *wg)
{
enum noise_handshake_state state = HANDSHAKE_ZEROED;
struct wg_peer *peer = NULL, *ret_peer = NULL;
struct noise_handshake *handshake;
u8 key[NOISE_SYMMETRIC_KEY_LEN];
u8 hash[NOISE_HASH_LEN];
u8 chaining_key[NOISE_HASH_LEN];
u8 e[NOISE_PUBLIC_KEY_LEN];
u8 ephemeral_private[NOISE_PUBLIC_KEY_LEN];
u8 preshared_key[NOISE_SYMMETRIC_KEY_LEN];
// 1. 加锁读取静态身份
down_read(&wg->static_identity.lock);
if (unlikely(!wg->static_identity.has_identity))
goto out;
// 2. 查找握手结构(通过接收者索引)
handshake = (struct noise_handshake *)wg_index_hashtable_lookup(
wg->index_hashtable, INDEX_HASHTABLE_HANDSHAKE, src->receiver_index, &peer);
if (unlikely(!handshake))
goto out;
// 3. 读取握手状态和临时数据
down_read(&handshake->lock);
state = handshake->state;
memcpy(hash, handshake->hash, NOISE_HASH_LEN);
memcpy(chaining_key, handshake->chaining_key, NOISE_HASH_LEN);
memcpy(ephemeral_private, handshake->ephemeral_private, NOISE_PUBLIC_KEY_LEN);
memcpy(preshared_key, handshake->preshared_key, NOISE_SYMMETRIC_KEY_LEN);
up_read(&handshake->lock);
// 4. 检查状态是否正确
if (state != HANDSHAKE_CREATED_INITIATION)
goto fail;
// 5. 处理临时公钥(e)
message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash);
// 6. ECDH:临时私钥 + 对端临时公钥(ee)
if (!mix_dh(chaining_key, NULL, ephemeral_private, e))
goto fail;
// 7. ECDH:静态私钥 + 对端临时公钥(se)
if (!mix_dh(chaining_key, NULL, wg->static_identity.static_private, e))
goto fail;
// 8. 混合预共享密钥(psk)
mix_psk(chaining_key, hash, key, preshared_key);
// 9. 解密空数据({})
if (!message_decrypt(NULL, src->encrypted_nothing, sizeof(src->encrypted_nothing),
key, hash))
goto fail;
// 10. 成功!复制所有信息到对等体
down_write(&handshake->lock);
// 检查状态是否仍然正确(加锁后)
if (handshake->state != state) {
up_write(&handshake->lock);
goto fail;
}
memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN);
memcpy(handshake->hash, hash, NOISE_HASH_LEN);
memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN);
handshake->remote_index = src->sender_index;
handshake->state = HANDSHAKE_CONSUMED_RESPONSE;
up_write(&handshake->lock);
ret_peer = peer;
goto out;
fail:
wg_peer_put(peer);
out:
memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
memzero_explicit(hash, NOISE_HASH_LEN);
memzero_explicit(chaining_key, NOISE_HASH_LEN);
memzero_explicit(ephemeral_private, NOISE_PUBLIC_KEY_LEN);
memzero_explicit(preshared_key, NOISE_SYMMETRIC_KEY_LEN);
up_read(&wg->static_identity.lock);
return ret_peer;
}
- 核心作用:解析 Type 2 包,验证身份,更新握手状态。
8. 阶段3:wg_noise_handshake_begin_session()
// 开始会话,派生双向密钥
bool wg_noise_handshake_begin_session(struct noise_handshake *handshake,
struct noise_keypairs *keypairs)
{
struct noise_keypair *new_keypair;
bool ret = false;
// 1. 加锁
down_write(&handshake->lock);
// 2. 检查状态是否正确
if (handshake->state != HANDSHAKE_CREATED_RESPONSE &&
handshake->state != HANDSHAKE_CONSUMED_RESPONSE)
goto out;
// 3. 创建新的密钥对
new_keypair = keypair_create(handshake->entry.peer);
if (!new_keypair)
goto out;
// 4. 设置发起方标志和远程索引
new_keypair->i_am_the_initiator = handshake->state == HANDSHAKE_CONSUMED_RESPONSE;
new_keypair->remote_index = handshake->remote_index;
// 5. 派生双向密钥
if (new_keypair->i_am_the_initiator)
derive_keys(&new_keypair->sending, &new_keypair->receiving, handshake->chaining_key);
else
derive_keys(&new_keypair->receiving, &new_keypair->sending, handshake->chaining_key);
// 6. 清零握手结构
handshake_zero(handshake);
// 7. 检查对等体是否已死亡
rcu_read_lock_bh();
if (likely(!READ_ONCE(container_of(handshake, struct wg_peer, handshake)->is_dead))) {
// 8. 添加新密钥对
add_new_keypair(keypairs, new_keypair);
net_dbg_ratelimited("%s: Keypair %llu created for peer %llu\n",
handshake->entry.peer->device->dev->name,
new_keypair->internal_id,
handshake->entry.peer->internal_id);
// 9. 替换索引哈希表中的条目
ret = wg_index_hashtable_replace(
handshake->entry.peer->device->index_hashtable,
&handshake->entry, &new_keypair->entry);
} else {
kfree_sensitive(new_keypair);
}
rcu_read_unlock_bh();
out:
up_write(&handshake->lock);
return ret;
}
派生双向会话密钥,创建密钥对结构,开始加密通信。
握手完成后的UDP数据处理
握手完成后,WireGuard 隧道的所有 UDP 传输包(除极特殊的防 DDoS Cookie 应答包外),均采用 ChaCha20-Poly1305 AEAD 算法进行全量加密+完整性认证,不存在明文传输的业务数据、控制包或隧道元数据,仅保留3个必要的明文字段用于快速路由和密钥匹配,且明文字段会被同步认证防篡改。
握手完成后,WireGuard 不再发送 Type1/2 握手包,所有隧道流量仅使用 Type3 数据报文(对应messages.h中定义的MESSAGE_DATA类型),包结构严格遵循内核源码定义,分为明文头部和加密载荷+认证标签两部分:
| 字段 | 长度 | 明文/密文 | 作用 |
|---|---|---|---|
| 魔数(Magic) | 4字节 | 明文 | WireGuard 包标识,固定值,用于快速过滤非 WireGuard 包 |
| 包类型 | 4字节 | 明文 | 固定为3,标识为数据传输包 |
| 接收者索引 | 4字节 | 明文 | 对应对等体密钥对的唯一ID,接收方无需解密即可快速定位密钥 |
| 加密载荷 | 可变长度 | 密文 | 原始三层IP包(用户的UDP/TCP/ICMP等所有业务流量) |
| Poly1305 认证标签 | 16字节 | 密文附属 | AEAD 算法生成的完整性校验值,防篡改、防伪造 |
发送侧:从TUN包到加密UDP包的全流程(对应内核源码)
https://github.com/WireGuard/wireguard-linux/blob/stable/drivers/net/wireguard/send.c
整个流程完全在内核态完成,无用户态-内核态切换,核心逻辑对应send.c+noise.c源码,步骤如下:
1. 流量捕获与对等体匹配
- 内核将发往
wg0虚拟网卡的原始IP包(无论UDP/TCP/ICMP),通过ndo_start_xmit钩子送入 WireGuard 驱动; - 通过
allowedips.c的二叉特里树,O(1)匹配到目标对等体,获取对等体当前有效的会话密钥对(struct noise_keypair,来自握手完成后wg_noise_handshake_begin_session派生的结果)。
2. 加密核心准备(对应noise.h密钥对结构)
// 你提供的noise.h中定义的密钥对核心结构
struct noise_keypair {
struct noise_symmetric_key sending; // 发送用对称密钥(握手派生)
atomic64_t sending_counter; // 64位原子递增计数器(nonce)
struct noise_symmetric_key receiving; // 接收用对称密钥
struct noise_replay_counter receiving_counter; // 接收端重放保护窗口
__le32 remote_index; // 对端索引(明文头部的接收者索引)
bool i_am_the_initiator; // 标记是否为握手发起方
};
- 原子递增
sending_counter,生成全局唯一的96位nonce(WireGuard 固定用64位计数器+32位0填充,符合ChaCha20-Poly1305规范),彻底避免nonce重用导致的加密安全问题; - 校验密钥有效性:若密钥已过期,触发自动重协商,拒绝发送明文包。
3. AEAD 全量加密(对应noise.c底层算法)
- 调用内核
chacha20poly1305_encrypt接口(noise.c中message_encrypt/message_decrypt复用的同一套算法),执行加密: - 用ChaCha20流加密算法,基于发送密钥+唯一nonce,加密原始IP包载荷;
- 用Poly1305算法,基于同一密钥,对「明文头部+加密载荷」生成16字节认证标签;
- 封装为完整的Type3包,填入明文头部、加密载荷、认证标签。
4. UDP发送
- 直接通过内核UDP套接字,将加密后的Type3包发送到对等体的公网端点(自动更新的NAT穿透地址);
- 全程无内存拷贝,直接操作内核
sk_buff缓冲区,极致优化转发性能。
接收侧:从UDP包到TUN注入的全流程(对应内核源码)
https://github.com/WireGuard/wireguard-linux/blob/stable/drivers/net/wireguard/receive.c
核心逻辑对应receive.c+noise.c源码,全程在内核态完成,先验签、后解密,验签失败直接丢弃,绝不处理非法包,步骤如下:
1. 包接收与快速路由
- 从内核UDP套接字收到包,先校验魔数和包类型,非Type3包直接分流到握手/防DDoS逻辑;
- 通过明文的「接收者索引」,从
index_hashtable中O(1)查找到对应的对等体和密钥对,无需解密即可完成路由,性能极致。
2. 防重放校验(对应noise.h重放计数器结构)
// 你提供的noise.h中定义的重放保护结构
struct noise_replay_counter {
u64 counter;
spinlock_t lock;
unsigned long backtrack[COUNTER_BITS_TOTAL / BITS_PER_LONG]; // 滑动窗口bitmap
};
- 校验包中的计数器:若计数器小于窗口最小值,或已在bitmap中标记为已接收,直接判定为重放攻击,丢弃包;
- 校验通过后,更新滑动窗口bitmap,标记该计数器为已接收,彻底杜绝重放攻击。
3. AEAD 解密与完整性校验
- 调用内核
chacha20poly1305_decrypt接口,先执行Poly1305验签: - 基于接收密钥+包中nonce,对「明文头部+加密载荷」重新生成认证标签,与包中标签比对;
- 验签失败直接丢弃包,绝不执行解密操作,抵御无效包的CPU消耗攻击;
- 验签通过后,用ChaCha20算法解密载荷,得到原始的三层IP包。
4. 流量注入与密钥状态更新
- 对解密后的IP包做合法性校验,通过后注入TUN虚拟网卡,交由内核网络栈后续处理;
- 若该包是
next_keypair的第一个有效包,调用wg_noise_received_with_keypair(你提供的noise.c原生函数),将新密钥升级为current_keypair,完成平滑密钥轮换。
特殊场景的加密处理
1. NAT保活包/空包
WireGuard 为维持NAT会话发送的空保活包,同样会经过完整的ChaCha20-Poly1305 AEAD加密,哪怕载荷长度为0,也会生成16字节的认证标签,不存在明文保活包,不会泄露隧道状态。
2. 防DDoS Cookie应答包(Type4)
这是握手完成后唯一的非Type3包,仅在受到UDP反射/DDoS攻击时触发: - 包中仅带防DDoS的Cookie认证信息,不带任何隧道业务数据、密钥信息或敏感内容; - 不加密,但带BLAKE2s强认证,仅用于验证发送方合法性,不会泄露任何隧道相关信息; - 正常通信场景下绝不会发送该类型包。
3. 密钥自动重协商
WireGuard 会定期(默认每2分钟)触发密钥重协商,重协商的Type1/2握手包,与初始握手逻辑完全一致:静态公钥、时间戳等敏感信息全程加密,无明文泄露。
WireGuard 加密算法体系详解
WireGuard 固定使用5类核心密码学原语,无任何可协商的算法选项,彻底杜绝算法降级攻击、错误配置导致的安全漏洞: | 算法类型 | 核心算法 | 安全强度 | 核心用途 | |----------|----------|----------|----------| | 非对称密钥交换 | Curve25519(X25519) | 128位 | 身份认证、ECDH密钥协商、前向保密 | | 对称加密+认证 | ChaCha20-Poly1305 AEAD | 256位密钥/128位安全 | 全量流量加密、完整性校验、防篡改 | | 加密哈希 | BLAKE2s | 256位输出/128位抗碰撞 | 哈希链、HMAC、密钥派生、防DDoS | | 密钥派生 | HKDF(基于BLAKE2s) | 128位 | 会话密钥派生、多轮熵混合 | | 后量子加固 | 可选256位PSK预共享密钥 | 256位 | 抗量子攻击、双重身份认证 |
1. Curve25519(X25519):非对称密钥交换与身份核心
Curve25519 是 Daniel J. Bernstein 设计的椭圆曲线 Diffie-Hellman(ECDH)密钥交换算法,RFC7748 标准化,是 WireGuard 整个加密体系的信任根。
源码对应位置(noise.c)
- 静态密钥生成:
wg_noise_set_static_identity_private_key()中通过curve25519_generate_public()从私钥派生公钥; - 临时密钥生成:
wg_noise_handshake_create_initiation()/wg_noise_handshake_create_response()中curve25519_generate_secret()生成单次握手的临时密钥; - ECDH 密钥协商:
mix_dh()/wg_noise_precompute_static_static()中curve25519()函数完成核心的密钥交换计算。
核心用途
- 身份标识:每个对等体的唯一身份是 32 字节的 Curve25519 静态公钥,替代传统 VPN 的证书体系,配置极简、无证书过期/吊销问题;
- 密钥协商:握手阶段通过多轮 ECDH 计算(静态-静态、临时-静态、临时-临时),生成会话密钥的种子;
- 前向保密:每次握手都会生成全新的临时 Curve25519 密钥对,用完即销毁;即使长期静态密钥泄露,历史会话流量也无法解密。
设计优势(对比 RSA/NIST P-256)
- 极致性能:同等安全强度下,计算量仅为 RSA2048 的 1/100,无大整数运算开销,嵌入式路由器/手机等弱算力设备也能轻松跑满带宽;
- 抗侧信道攻击:全程常数时间实现,无分支判断,彻底规避时序攻击、缓存攻击等侧信道风险;
- 无潜在后门:曲线参数完全公开、可验证,规避了 NIST 系列曲线的参数不透明问题;
- 极简轻量:32 字节公钥/私钥,仅为 RSA2048 公钥长度的 1/8,包体积更小、配置更简单。
2. ChaCha20-Poly1305 AEAD:对称加密与完整性认证核心
ChaCha20-Poly1305 是 RFC8439 标准化的带关联数据的认证加密(AEAD) 算法,由 ChaCha20 流加密算法 + Poly1305 消息认证码组合而成,是 TLS 1.3 的标配算法,也是 WireGuard 唯一的对称加密算法。
源码对应位置(noise.c)
- 握手阶段加密:
message_encrypt()/message_decrypt()中完成静态公钥、时间戳的加解密与认证; - 数据流量加密:
send.c/receive.c中复用同一套算法,完成所有 Type3 数据报文的加解密; - 底层实现:直接调用 Linux 内核的
chacha20poly1305_encrypt()/chacha20poly1305_decrypt()接口。
核心用途
- 全量流量加密:握手完成后,所有隧道内的 IP 包(UDP/TCP/ICMP 等)全部通过 ChaCha20 加密,无任何明文业务数据;
- 完整性与真实性认证:Poly1305 为每个包生成 16 字节的认证标签,先验签、后解密,验签失败的包直接丢弃,彻底杜绝篡改、伪造、重放攻击;
- 明文头部保护:包的明文字段(魔数、类型、接收者索引)会被纳入 AEAD 的「关联数据(AD)」进行认证,即使明文字段被篡改,也会直接验签失败。
设计优势(对比 AES-GCM)
- 无硬件依赖,全场景高性能:纯软件实现,在无 AES-NI 硬件加速的设备(嵌入式路由器、手机、低功耗开发板)上,性能比 AES-GCM 高 3-5 倍;有硬件加速的场景下,性能也与 AES-GCM 持平;
- 抗侧信道攻击:流加密设计,无 S 盒查表操作,全程常数时间实现,规避了 AES 常见的缓存时序攻击;
- AEAD 一体设计:加密与认证在一个原子步骤完成,彻底避免了「先加密后认证」的经典安全错误(如 OpenSSL 历史漏洞);
- Nonce 安全设计:WireGuard 固定使用 64 位原子递增计数器作为 Nonce 核心,保证每个包的 Nonce 全局唯一,彻底杜绝 Nonce 重用导致的加密密钥泄露风险。
3. BLAKE2s:加密哈希函数核心
BLAKE2s 是 Daniel J. Bernstein 设计的加密哈希函数,RFC7693 标准化,是 NIST SHA-3 竞赛的决赛入围算法,256 位输出,提供 128 位抗碰撞安全强度。
源码对应位置(noise.c)
- 全局初始化:
wg_noise_init()中通过 BLAKE2s 生成 Noise 协议的初始哈希链与 chaining key; - 哈希链更新:
mix_hash()函数完成握手阶段的哈希状态迭代; - 底层支撑:
hmac()/kdf()函数的核心均基于 BLAKE2s 实现; - 防DDoS:Cookie 生成与验证的核心哈希算法。
核心用途
- Noise 协议哈希链:维护握手过程中的哈希状态,保证握手消息的完整性,防止握手包被篡改;
- HMAC 实现:作为 HKDF 密钥派生、Cookie 认证的底层哈希函数;
- 协议绑定:初始哈希值绑定了 WireGuard 协议标识与 Noise 协议名称,防止跨协议攻击。
设计优势(对比 SHA-256/SHA-3)
- 极致性能:在 32 位/64 位设备上,软件实现性能均远超 SHA-256,尤其适合 Linux 内核态的轻量实现;
- 安全冗余:通过了 NIST 严格的密码学分析,无任何已知安全漏洞,抗碰撞、抗第二原像攻击能力与 SHA-256 完全对齐;
- 功能灵活:原生支持密钥化哈希,无需额外封装 HMAC,代码量更小、实现更简洁。
4. HKDF:密钥派生函数
HKDF(HMAC-based Extract-and-Expand Key Derivation Function)是 RFC5869 标准化的密钥派生函数,WireGuard 基于 BLAKE2s-HMAC 实现,是连接 ECDH 密钥协商与对称加密的核心桥梁。
源码对应位置(noise.c)
- 核心实现:
kdf()函数完整实现了 HKDF 的 Extract 与 Expand 阶段; - 密钥混合:
mix_dh()中通过 HKDF 混合 ECDH 结果,更新 chaining key; - 会话密钥派生:
derive_keys()中通过 HKDF 从最终 chaining key 派生出双向独立的发送/接收密钥。
核心用途
- 熵提取:从多轮 ECDH 计算的结果中提取均匀的密码学熵,即使 ECDH 结果存在少量熵不足,也能生成安全的密钥种子;
- 密钥分离:从单一的 chaining key 派生出完全独立的发送密钥、接收密钥,保证双向流量的加密密钥互不影响,单向密钥泄露不会导致全量流量解密;
- 密钥扩展:按需扩展出符合加密算法要求的密钥长度,避免密钥复用导致的安全风险。
设计优势
- 标准化实现:采用业界通用的密钥派生标准,彻底规避了自定义密钥派生的常见安全漏洞;
- 分层安全:Extract 与 Expand 两个阶段分离,先保证熵的安全性,再扩展出可用密钥,密码学设计严谨;
- 轻量高效:基于 BLAKE2s 实现,代码量不足 50 行,内核态运行开销极低。
5. 可选 PSK 预共享密钥:后量子安全加固
WireGuard 支持可选配置 256 位预共享密钥(PSK),对应 Noise_IKpsk2 协议中的 psk 环节,是针对量子计算攻击的额外加固层。
源码对应位置(noise.c)
- 初始化:
wg_noise_handshake_init()中复制对等体的 PSK 到握手结构; - 密钥混合:
mix_psk()函数完成 PSK 与 chaining key 的混合,派生最终的加密密钥; - 握手流程:
wg_noise_handshake_create_response()/wg_noise_handshake_consume_response()中调用 PSK 混合逻辑。
核心用途
- 后量子安全:即使未来 Curve25519 椭圆曲线被量子计算机破解,只要 PSK 不泄露,历史会话与新建会话的流量都无法解密;
- 双重身份认证:在静态公钥的基础上,额外增加一层对称密钥认证,只有同时持有正确公钥和 PSK 的对等体才能完成握手。
三、辅助安全机制(配套算法实现)
- TAI64N 时间戳:
noise.c中tai64n_now()函数生成,握手阶段加密传输,用于防止握手包的重放攻击,保证每个握手包的唯一性; - 滑动窗口重放保护:
noise.h中noise_replay_counter结构实现,基于 bitmap 滑动窗口,防止数据报文的重放攻击,64 位计数器保证每个包的序号唯一,无重复、无遗漏。