Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VMessAEAD 设计文档 #20

Closed
kslr opened this issue Aug 7, 2020 · 0 comments
Closed

VMessAEAD 设计文档 #20

kslr opened this issue Aug 7, 2020 · 0 comments

Comments

@kslr
Copy link
Contributor

kslr commented Aug 7, 2020

1. 现状

目前 VMess 已可根据协议头自动协商使用 AEAD 加密并认证传输的流量。但 VMess 协议头部仍然使用 MD5 + AES-CFB ,不能保证协议头部自身的完整性。

2. 定义

KDF: KDF 接受一个字节数组形式的主密钥,和若干字节数组形式的路径,生成一个子密钥

func KDF(key []byte, path [][]byte) []byte {
    oKDF = HMAC(SHA256, "VMess AEAD KDF")
    for in_v := range path {
        oKDF = HMAC(oKDF, in_v)
    }
    return oKDF(key)
}

CmdKey: 由用户 UUID 产生的一个字节数组形式密钥

const IDBytesLen = 16

type ID struct {
    uuid   uuid.UUID
    cmdKey [IDBytesLen]byte
}

func (id ID) CmdKey() []byte {
    return id.cmdKey[:]
}

3. EAuID

为了保持和原有协议,配置兼容,继而保证全部用户都可以享受到这个更新,必须要通过且仅通过最开始16个字节计算出客户端的UUID信息。

3.1 EAuID

EAuID 明文为 [Timestamp 8B][Rand 4B][CRC 4B]

Timestamp 为以秒为单位的64位 Unix 时间戳
Rand 为随机数
CRC 为 CRC32([Timestamp, Rand]) IEEE 多项式

EAuID 密钥为 KDF(CmdKey, ["AES Auth ID Encryption"])
EAuID 使用 AES-128 块加密

3.2 ELength

ELength 是经过 AES-128-GCM 加密的 16 位大端序 VMess 包头部长度。
加密密钥为 KDF(CmdKey, ["VMess Header AEAD Key_Length", EAuID, Nonce])
不重数为 KDF(CmdKey, ["VMess Header AEAD Nonce_Length", EAuID, Nonce])
附加数据为 EAuID

3.3 EHeader

经过 AES-128-GCM 加密的 VMess 标准首部
加密密钥为 KDF(CmdKey, ["VMess Header AEAD Key", EAuID, Nonce])
不重数为 KDF(CmdKey, ["VMess Header AEAD Nonce", EAuID, Nonce])
附加数据为 EAuID

3.4 整体格式

[EAuID][ELength][Rand 8B][EHeader]

Rand 是 64 位随机数

3.5 验证流程

对于每个客户端发来的 EAuID

  1. 进行重放检测,如果之前 120 秒内见过这个 EAuID 就终止检测,结果为重放错误。
  2. 针对每一个服务器上用户的 UUID。
  3. 根据 UUID 生成 CmdKey(用戶 UUID),并解密 EAuID。
  4. 校验 AuID.zero 如果 CRC32 结果错误终止第二步 2 的本轮循环,继续尝试下一个 UUID。
  5. 校验 AuID.Time 如果时间差超过允许的数值结果错误终止第二步 2 的本轮循环,继续尝试下一个 UUID。
  6. 如果进行至这步校验完成,终止检测 结果为 初步认证成功 本轮的 UUID 为认证用户的 UUID。

4. VMessAEAD 包头

4.1 ALength

AES-128-GCM 加密的 16 位大端序包头部长度。
加密密钥为 KDF(CmdKey, ["VMess Header AEAD Key_Length", EAuID, Nonce])[:16]
不重数为 KDF(CmdKey, ["Vmess Header AEAD Nonce_Length", EAuID, Nonce])[:12] 附加数据为 EAuID

目的在与尽早完成对客户端的验证(注1)

  • 必须发送长度否则不知道要读取多少信息
  • 必须验证 EAuID,防止攻击者抓取阻止新请求后用新 EAuID 和旧的 Nonce,MaskedLength 一起构造重放攻击
  • 必须验证 Length,防止攻击者修改长度,后面的 MaskedLength 没有独立的认证
  • 必须验证 Nonce,如果 Nonce 错误解密会失败,要尽早验证 (注1)
  • 必须加盐,此用户 UUID (CmdKey) 还有其他用途

4.2 Aheader

经过 AES-128-GCM 加密的 VMess AEAD 首部
加密密钥为 KDF(CmdKey, ["VMess Header AEAD Key", EAuID, Nonce])[:16]
不重数为 KDF(CmdKey, ["VMess Header AEAD Nonce", EAuID, Nonce])[:12] 附加数据为 EAuID

数据是VMess的整个未加密头部,从版本号到校验数据。目的是简化支持AEAD的难度,减少非必要的协议修改。

AES-GCM 要求 Key-Nonce 组合不重复。由于其中使用了 EAuID, Nonce 作为Key,Nonce 的 KDF 输入,有96位随机熵输入不会重复。因为使用用户 UUID 作为输入无法被攻击者解密。 这两个 AEAD 输入都必须加盐因为用户 UUID 还有其他用途。

4.3 整体格式

[EAuID][ALength][Rand 8B][Aheader]

Rand 是 64 位随机数

4.4 VMessAEAD 处理流程

  1. 读取 EAuID / 或原协议认证信息 16 字节。
  2. 根据 EAuID / 或原协议认证信息判断是否应该使用 AEAD 方法读取协议头。如果是旧协议的话就用原来的方法并返回结果。
  3. 继续读取 AuIDCheck,MaskedLength,Nonce。此时已经读取了 42 字节 <= 38 + 16 Drain 的初始值。
  4. 解除 MaskedLength 的混淆为 Length。
  5. 验证 AuIDCheck 数值,如果不正确则进行 Drain。
  6. 读取 AEADVMessHeader,解密为 VMessHeader 并进行相应处理并返回结果。

VMessAEAD 协议的返回头也会使用 AEAD 方式进行加密,方法类似请求包头。但是密钥数据来自连接密钥 + KDF。发送 AEAD 加密的 MaskedLength,之后是内容。

客户端必须记忆自己是否使用了 VMessAEAD 发送包头,在使用 VMessAEAD 替换掉了一些 MD5 等弱密码学函数,即使这些弱密码学函数尚不构成任何已知协议弱点。

注1:
阻止抓包 + 重放攻击者根据读取长度来确定服务器类型,如果需要读取过多数据,那么就会超出计算出的 Drain 值长度,无法通过 Drain 隐藏服务器处理流程。
为了保证 Drain 能正常运行,必须在读取 38 + 16 个字节前决定此连接是否来自真实客户端。

  • AuIDCheck 保证在读取 42 字节时已经得到 211 位校验熵,确定不是来自抓包 + 部分重放。
@kslr kslr changed the title VMessAEAD 设计文档 VMessAEAD 设计文档 [草稿] Aug 7, 2020
@kslr kslr changed the title VMessAEAD 设计文档 [草稿] VMessAEAD 设计文档 Aug 8, 2020
@kslr kslr added the standard label Aug 9, 2020
@kslr kslr reopened this Jul 22, 2021
@kslr kslr reopened this Sep 28, 2021
@github-actions github-actions bot closed this as completed Dec 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant