Skip to content

VLESS 后量子加密层

位置:核心协议层 | 难度:专家 | 前置密码学基础VLESS 协议源码证据proxy/vless/encryption/client.go(211行)、server.go(329行)、common.go(281行)、xor.go(94行)


1. 定位

VLESS 标准模式使用 "encryption": "none",安全由外层 TLS/REALITY 保证。但当 VLESS 不经过 TLS(如 CDN 前置场景中的 0-RTT 连接)时,需要应用层加密。

proxy/vless/encryption/ 实现了一套完整的后量子安全加密体系,包含:

  • 双层密钥交换:NFS(Non-Forward-Secret,非前向安全)层 + PFS(Perfect Forward Secrecy,前向安全)层
  • ML-KEM-768 + X25519 混合密钥交换:同时抵抗经典和量子攻击
  • 0-RTT 会话恢复:基于 ticket 的快速重连
  • 可配置流量模式混淆:分片填充 + 时序间隔
  • XOR 流加密模式:消除密文特征

2. 密钥层次架构

配置密钥 (nfsSKeysBytes, 服务端持有私钥)

  └── NFS 层:ECDH 或 ML-KEM-768 密钥交换

        ├── NFS Key (nfsKey)
        │     └── 用于加密 PFS 层的 Key Exchange 和 ticket

        └── PFS 层:ML-KEM-768 + X25519 混合密钥交换

              ├── ML-KEM-768 Shared Secret (32 bytes)
              ├── X25519 Shared Secret (32 bytes)

              └── PFS Key = mlkem768Key || x25519Key (64 bytes)

UnitedKey = PFS Key || NFS Key (96 bytes)

  ├── Upload AEAD 密钥 = BLAKE3-DeriveKey(upload_ctx, UnitedKey)
  └── Download AEAD 密钥 = BLAKE3-DeriveKey(download_ctx, UnitedKey)

为什么是双层

  • NFS 层:服务端持有长期私钥。客户端可以离线计算 NFS Key,实现 0-RTT。不提供前向安全——如果服务端私钥泄露,历史 NFS Key 可被计算。
  • PFS 层:每次连接生成临时密钥对。提供前向安全——即使服务端和客户端的长期私钥全部泄露,历史会话仍然安全。

3. NFS 密钥交换

NFS 支持两种密钥交换算法:

算法私钥长度公钥长度中继载荷0-RTT 支持
X2551932 bytes32 bytes32 + 32B
ML-KEM-7682400 bytes1184 bytes1088 + 32B否(封装是单向的)

3.1 客户端初始化

源码位置:encryption/client.go 行 34-63

go
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
    l := len(nfsPKeysBytes)
    i.NfsPKeys = make([]any, l)
    i.Hash32s = make([][32]byte, l)
    for j, k := range nfsPKeysBytes {
        if len(k) == 32 {
            i.NfsPKeys[j], _ = ecdh.X25519().NewPublicKey(k)     // X25519 公钥
            i.RelaysLength += 32 + 32
        } else {
            i.NfsPKeys[j], _ = mlkem.NewEncapsulationKey768(k)   // ML-KEM-768 封装密钥
            i.RelaysLength += 1088 + 32
        }
        i.Hash32s[j] = blake3.Sum256(k)
    }
}

每个 NFS 公钥后面会跟随下一个公钥的 BLAKE3 哈希值(32 字节),用于服务端验证中继链的完整性。

3.2 中继链(Relay Chain)

当配置了多个 NFS 密钥时,形成一条中继链:

Relay[0] = Encrypt(X25519_public_key_0)
         || Encrypt(BLAKE3(nfsPKeyBytes[1]))
Relay[1] = Encrypt(X25519_public_key_1)
         || Encrypt(BLAKE3(nfsPKeyBytes[2]))
...

每一层使用上一层的 NFS Key 派生的 CTR 流加密下一层的公钥和哈希。这创建了一个必须按顺序解密的链——服务端必须持有所有对应私钥才能完整解开。

源码位置:encryption/client.go 行 98-109

go
if lastCTR != nil {
    lastCTR.XORKeyStream(relays, relays[:32]) // 用上一层的 CTR 加密这一层
}
// ...
if j == len(i.NfsPKeys)-1 {
    break
}
lastCTR = NewCTR(nfsKey, iv)
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) // 加密下一层的哈希

3.3 XOR 模式

源码位置:encryption/client.go 行 98-100,encryption/xor.go

配置项 xorMode 控制公钥在传输中的外观:

模式行为
无 XOR0公钥以原始随机字节形式传输
XOR 公钥1公钥与 CTR 流 XOR——使公钥字节区别于随机数据
XOR 全部2对 5 字节 TLS 记录头进行 XOR 以消除 TLS 特征 + 对公钥 XOR

XOR 模式 2 的核心实现xor.go 行 41-67):

XorConn 包装 net.Conn,在 Read/Write 时自动对 TLS 记录头(前 5 字节)进行 XOR。接收方需要反操作来恢复原始数据:

go
func (c *XorConn) Write(b []byte) (int, error) {
    // 1. 跳过已处理的字节
    // 2. 读取 5 字节 TLS 记录头
    // 3. CTR XOR 记录头 → 原始 TLS 特征消失
    // 4. 解析记录长度,跳过载荷部分
    // 5. 写入原始连接
}

func (c *XorConn) Read(b []byte) (int, error) {
    // 反向操作:读取 → XOR 恢复记录头 → 解析长度
}

这使数据流在网络层面看起来不像 TLS——消除了 TLS 记录头的固定格式特征(0x17 0x03 0x03)。即使没有外层 TLS 伪装,裸 VLESS 加密流量也不会有可识别的 TLS 特征。


4. PFS 密钥交换

4.1 完整 1-RTT 握手

4.2 为什么混合 ML-KEM-768 + X25519

ML-KEM-768(Module-Lattice-based Key Encapsulation Mechanism)是 NIST 标准化的后量子密钥封装算法。它抵抗 Shor 算法在量子计算机上的攻击。但:

  1. ML-KEM-768 作为较新的算法,可能在未来发现实现缺陷或新的数学攻击
  2. X25519 经过了十余年的广泛密码分析,安全性已充分验证

混合方案确保:攻击者需要同时破解 ML-KEM-768 X25519 才能恢复 PFS Key。任何一个算法的安全性就能保证整体安全。

4.3 客户端 PFS 交换

源码位置:encryption/client.go 行 131-136

go
pfsKeyExchange := clientHello[ivAndRelaysLength : ivAndRelaysLength+pfsKeyExchangeLength]
nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
mlkem768DKey, _ := mlkem.GenerateKey768()
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)

pfsPublicKey 的格式:

[ML-KEM-768 封装密钥: 1184 bytes] [X25519 公钥: 32 bytes]
总长:1216 bytes

这 1216 字节使用 NFS AEAD 加密,对外表现为随机数据。


5. 0-RTT 会话恢复

当客户端在 seconds 配置的时间窗口内重连同一服务端时,可以跳过 PFS 交换:

源码位置:encryption/client.go 行 113-128

go
if i.Seconds > 0 {
    i.RWLock.RLock()
    if time.Now().Before(i.Expire) {
        c.Client = i
        c.UnitedKey = append(i.PfsKey, nfsKey...)
        nfsAEAD.Seal(clientHello[:ivAndRelaysLength], nil, EncodeLength(32), nil) // length=32 表示 0-RTT
        nfsAEAD.Seal(clientHello[:ivAndRelaysLength+18], nil, i.Ticket, nil)       // 发送 ticket
        i.RWLock.RUnlock()
        c.PreWrite = clientHello[:ivAndRelaysLength+18+32]
        // ...
    }
}

流程:

  1. 客户端发送 length=32(0-RTT 信号) + 加密的 ticket
  2. 服务端解密 ticket → 查找缓存的 ServerSession → 获取 PfsKey
  3. 服务端用 PFS Key + 本次 NFS Key 组合 UnitedKey
  4. 无需 PFS 交换,直接建立加密通道
  5. 服务端检查 NfsKeys.LoadOrStore(nfsKey, true) 防止重放攻击

6. BLAKE3 密钥派生

所有 AEAD 密钥通过 BLAKE3 的 DeriveKey 函数从 UnitedKey 派生:

源码位置:encryption/common.go 行 157-168

go
func NewAEAD(ctx, key []byte, useAES bool) *AEAD {
    k := make([]byte, 32)
    blake3.DeriveKey(k, string(ctx), key)  // ctx = 上下文(如客户端公钥字节)
    var aead cipher.AEAD
    if useAES {
        block, _ := aes.NewCipher(k)
        aead, _ = cipher.NewGCM(block)
    } else {
        aead, _ = chacha20poly1305.New(k)
    }
    return &AEAD{AEAD: aead}
}

ctx 参数是可变长度的唯一上下文,确保每条通信方向的 AEAD 密钥不同:

  • 客户端→服务端上传密钥:ctx = pfsPublicKey(1216 bytes,来自客户端握手)
  • 服务端→客户端下载密钥:ctx = serverPfsKey(1120 bytes,来自服务端握手)
  • 0-RTT 上传密钥:ctx = clientRandom(16 bytes,客户端生成)
  • 0-RTT 下载密钥:ctx = encryptedTicket(32 bytes,来自 ticket)

这种设计防止了密钥重用攻击——即使上传和下载使用相同的 UnitedKey,派生的 AEAD 密钥因 ctx 不同而完全不同。

为什么用 BLAKE3 而不是 HKDF

BLAKE3 的 DeriveKey 提供与 HKDF 类似的密钥派生功能,但具有更高的性能和更简洁的 API。对于 VLESS 加密层的场景(高吞吐量代理),BLAKE3 的 SIMD 优化带来显著的性能优势。


7. Nonce 管理

源码位置:encryption/common.go 行 170-194

VLESS 加密层使用递增 Nonce 策略:

go
func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
    if nonce == nil {
        nonce = IncreaseNonce(a.Nonce[:])  // 自动递增
    }
    return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
}

func IncreaseNonce(nonce []byte) []byte {
    for i := range 12 {
        nonce[11-i]++     // 小端序 96-bit 计数器递增
        if nonce[11-i] != 0 {
            break
        }
    }
    return nonce
}

每次 Seal 或 Open 调用时 Nonce 自动递增 1。当 Nonce 达到最大值(MaxNonce = 0xFFFFFFFFFFFF)时,使用该消息的密文 + 头部作为新的 ctx 派生下一轮 AEAD 密钥。

源码位置:encryption/common.go 行 62-67

go
if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {
    c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)
}

这确保了即使传输海量数据,每个 Nonce 在同一个 AEAD 密钥下不会重复(GCM 的 Nonce 重复会导致灾难性安全破坏)。


8. 流量模式混淆

8.1 分片填充

源码位置:encryption/common.go 行 259-280

go
func CreatPadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) {
    // paddingLens 格式:[概率, 最小长度, 最大长度]
    // paddingGaps 格式:[概率, 最小间隔, 最大间隔]
    for _, y := range paddingLens {
        if y[0] >= int(crypto.RandBetween(0, 100)) {
            l = int(crypto.RandBetween(int64(y[1]), int64(y[2])))
        }
        lens = append(lens, l)
    }
    // ...
}

客户端和服务端各生成一段填充数据,由 nfsAEAD.Seal 加密后以分片形式发送,每个分片之间插入随机时间间隔。效果:

  • 握手阶段的数据包大小不是固定值
  • 数据包之间的时序间隔不是均匀分布
  • 审查方无法通过"握手包大小/时序"来识别 VLESS 加密握手

8.2 TLS 记录头伪装

源码位置:encryption/common.go 行 204-210

go
func EncodeHeader(h []byte, l int) {
    h[0] = 23     // 0x17 = Application Data
    h[1] = 3      // TLS Major Version
    h[2] = 3      // TLS Minor Version = 0x0303 (TLS 1.2 的 Wire Version)
    h[3] = byte(l >> 8)
    h[4] = byte(l)
}

加密数据使用 TLS Application Data 记录头(17 03 03)封装。即使在网络层面观察,数据流也表现为连续的 TLS 1.2 应用数据记录——与普通 HTTPS 的加密数据流量完全一致。

结合 XOR 模式 2(xor.go),这个 TLS 头部可以被 XOR 流密码覆盖,使数据流进一步失去 TLS 特征。


9. 源码文件索引

文件核心内容
encryption/common.goCommonConn 读写、AEAD Nonce 管理、NewAEAD BLAKE3 派生、分片填充
encryption/client.goClientInstance 握手、NFS 中继链、PFS 交换、0-RTT ticket
encryption/server.goServerInstance 握手、NFS 链解密、PFS 响应、ticket 管理
encryption/xor.goXorConn CTR 流加密、NewCTR BLAKE3 密钥派生
encoding/addons.goVLESS Addons 编解码、XUDP MultiLengthPacketWriterLengthPacketReader

10. 与 REALITY 的协同

VLESS 加密层和 REALITY 是互补而非互斥的两套安全机制:

维度VLESS 加密层REALITY
工作层面应用层(TLS 记录内部)TLS 握手层
提供后量子加密 + 0-RTT + 流量混淆TLS 握手伪装 + 抗主动探测
适用场景CDN 前置(0-RTT)、非 TLS 传输直连(对审查方完全透明)

两者可以同时启用:REALITY 处理 TLS 握手伪装,VLESS 加密层处理 TLS 隧道内部的 PFS 加密和流量混淆。


11. 外部参考资料

资料:FIPS 203 — Module-Lattice-based Key-Encapsulation Mechanism Standard 类型:NIST 标准 链接:https://csrc.nist.gov/pubs/fips/203/final 可信度:A

资料:BLAKE3 — one function, fast everywhere 类型:项目文档 链接:https://github.com/BLAKE3-team/BLAKE3-specs 可信度:A

资料:RFC 9180 — Hybrid Public Key Encryption (HPKE) 类型:IETF 标准 链接:https://www.rfc-editor.org/rfc/rfc9180 可信度:A