Skip to content

2026

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.cmessage_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() 函数完成核心的密钥交换计算。
核心用途
  1. 身份标识:每个对等体的唯一身份是 32 字节的 Curve25519 静态公钥,替代传统 VPN 的证书体系,配置极简、无证书过期/吊销问题;
  2. 密钥协商:握手阶段通过多轮 ECDH 计算(静态-静态、临时-静态、临时-临时),生成会话密钥的种子;
  3. 前向保密:每次握手都会生成全新的临时 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() 接口。
核心用途
  1. 全量流量加密:握手完成后,所有隧道内的 IP 包(UDP/TCP/ICMP 等)全部通过 ChaCha20 加密,无任何明文业务数据;
  2. 完整性与真实性认证:Poly1305 为每个包生成 16 字节的认证标签,先验签、后解密,验签失败的包直接丢弃,彻底杜绝篡改、伪造、重放攻击;
  3. 明文头部保护:包的明文字段(魔数、类型、接收者索引)会被纳入 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 生成与验证的核心哈希算法。
核心用途
  1. Noise 协议哈希链:维护握手过程中的哈希状态,保证握手消息的完整性,防止握手包被篡改;
  2. HMAC 实现:作为 HKDF 密钥派生、Cookie 认证的底层哈希函数;
  3. 协议绑定:初始哈希值绑定了 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 派生出双向独立的发送/接收密钥。
核心用途
  1. 熵提取:从多轮 ECDH 计算的结果中提取均匀的密码学熵,即使 ECDH 结果存在少量熵不足,也能生成安全的密钥种子;
  2. 密钥分离:从单一的 chaining key 派生出完全独立的发送密钥、接收密钥,保证双向流量的加密密钥互不影响,单向密钥泄露不会导致全量流量解密;
  3. 密钥扩展:按需扩展出符合加密算法要求的密钥长度,避免密钥复用导致的安全风险。
设计优势
  • 标准化实现:采用业界通用的密钥派生标准,彻底规避了自定义密钥派生的常见安全漏洞;
  • 分层安全: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 混合逻辑。
核心用途
  1. 后量子安全:即使未来 Curve25519 椭圆曲线被量子计算机破解,只要 PSK 不泄露,历史会话与新建会话的流量都无法解密;
  2. 双重身份认证:在静态公钥的基础上,额外增加一层对称密钥认证,只有同时持有正确公钥和 PSK 的对等体才能完成握手。

三、辅助安全机制(配套算法实现)

  1. TAI64N 时间戳noise.ctai64n_now() 函数生成,握手阶段加密传输,用于防止握手包的重放攻击,保证每个握手包的唯一性;
  2. 滑动窗口重放保护noise.hnoise_replay_counter 结构实现,基于 bitmap 滑动窗口,防止数据报文的重放攻击,64 位计数器保证每个包的序号唯一,无重复、无遗漏。