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 支持 |
|---|---|---|---|---|
| X25519 | 32 bytes | 32 bytes | 32 + 32B | 是 |
| ML-KEM-768 | 2400 bytes | 1184 bytes | 1088 + 32B | 否(封装是单向的) |
3.1 客户端初始化
源码位置:encryption/client.go 行 34-63
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
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 控制公钥在传输中的外观:
| 模式 | 值 | 行为 |
|---|---|---|
| 无 XOR | 0 | 公钥以原始随机字节形式传输 |
| 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。接收方需要反操作来恢复原始数据:
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 算法在量子计算机上的攻击。但:
- ML-KEM-768 作为较新的算法,可能在未来发现实现缺陷或新的数学攻击
- X25519 经过了十余年的广泛密码分析,安全性已充分验证
混合方案确保:攻击者需要同时破解 ML-KEM-768 和 X25519 才能恢复 PFS Key。任何一个算法的安全性就能保证整体安全。
4.3 客户端 PFS 交换
源码位置:encryption/client.go 行 131-136
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
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]
// ...
}
}流程:
- 客户端发送
length=32(0-RTT 信号) + 加密的 ticket - 服务端解密 ticket → 查找缓存的
ServerSession→ 获取PfsKey - 服务端用 PFS Key + 本次 NFS Key 组合
UnitedKey - 无需 PFS 交换,直接建立加密通道
- 服务端检查
NfsKeys.LoadOrStore(nfsKey, true)防止重放攻击
6. BLAKE3 密钥派生
所有 AEAD 密钥通过 BLAKE3 的 DeriveKey 函数从 UnitedKey 派生:
源码位置:encryption/common.go 行 157-168
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 策略:
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
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
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
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.go | CommonConn 读写、AEAD Nonce 管理、NewAEAD BLAKE3 派生、分片填充 |
encryption/client.go | ClientInstance 握手、NFS 中继链、PFS 交换、0-RTT ticket |
encryption/server.go | ServerInstance 握手、NFS 链解密、PFS 响应、ticket 管理 |
encryption/xor.go | XorConn CTR 流加密、NewCTR BLAKE3 密钥派生 |
encoding/addons.go | VLESS Addons 编解码、XUDP MultiLengthPacketWriter、LengthPacketReader |
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