V2Fly.orgV2Fly.org
快速开始
配置文档
配置文档 (v5, WIP)
工具列表
开发手册
新白话文指南
  • 简体中文
  • English
GitHub
快速开始
配置文档
配置文档 (v5, WIP)
工具列表
开发手册
新白话文指南
  • 简体中文
  • English
GitHub
  • 开发手册

    • 第一步:学会编译
    • 第二步:了解架构
    • 第三步:开始开发
    • Release Archive
    • Release Signing
  • 协议细节

    • VMess 协议
      • 版本
      • 依赖
        • 底层协议
        • 用户 ID
        • 函数
      • 通讯过程
      • 客户端请求
        • AEAD 认证格式
        • MD5 认证格式(弃用)
        • 指令部分(通用)
        • 数据部分(通用)
      • 服务器应答
        • AEAD 认证响应
        • MD5 认证响应(弃用)
        • 响应头部格式
        • 动态端口指令
      • 注释
    • mKCP 协议
    • Mux.Cool 协议

VMess 协议

VMess 是 V2Ray 原创的加密通讯协议。

VMess 协议头部有两种认证方式:

  • AEAD 认证:使用 AEAD 加密方式,保证协议头部的完整性;
  • MD5 认证:传统的认证方式,使用 MD5 + AES-128-CFB 加密协议头部,不能保证协议头部自身的完整性。

注意

MD5 认证方式已弃用,请使用 AEAD 认证方式。

目前 VMess 可根据协议头自动协商使用 AEAD 或 MD5 认证方式。

版本

当前版本号为 1。

依赖

底层协议

VMess 是一个基于 TCP 的协议,所有数据使用 TCP 传输。

用户 ID

ID 等价于 UUID,是一个 16 字节长的随机数,它的作用相当于一个令牌(Token)。 一个 ID 形如:de305d54-75b4-431b-adb2-eb6b9e546014,几乎完全随机,可以使用任何的 UUID 生成器来生成,比如这个。

用户 ID 可在配置文件中指定。

函数

  • MD5: MD5 函数

    • 输入参数为任意长度的 byte 数组
    • 输出为一个 16 byte 的数组
  • HMAC: HMAC 函数

    • 输入参数为:
      • H:散列函数
      • K:密钥,任意长度的 byte 数组
      • M:消息,任意长度的 byte 数组
  • Shake: SHA3-Shake128 函数

    • 输入参数为任意长度的 byte 数组
    • 输出为任意长度的 byte 数组
  • KDF: 密钥派生函数,接受一个字节数组形式的主密钥,和若干字节数组形式的路径,生成一个子密钥(用于 AEAD 认证)

    KDF(key, path...):
        hmac_creator = HMAC(SHA256, "VMess AEAD KDF")
        for each p in path:
            hmac_creator = HMAC(hmac_creator, p)
        return hmac_creator(key)
    
  • CmdKey: 由用户 UUID 产生的一个 16 字节密钥(用于 AEAD 认证)

    CmdKey = MD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
    

通讯过程

VMess 是一个无状态协议,即客户端和服务器之间不需要握手即可直接传输数据,每一次数据传输对之前和之后的其它数据传输没有影响。

VMess 的客户端发起一次请求,服务器判断该请求是否来自一个合法的客户端。如验证通过,则转发该请求,并把获得的响应发回给客户端。

VMess 使用非对称格式,即客户端发出的请求和服务器端的响应使用了不同的格式。

客户端请求

客户端请求由三部分组成:认证信息、指令部分、数据部分。根据认证方式不同,认证信息和指令部分的格式有所区别。

AEAD 认证格式

16 字节18 字节8 字节Y 字节余下部分
EAuIDALengthNonceAHeader数据部分

其中:

  • EAuID:加密认证 ID,用于识别用户身份;
  • ALength:加密后的指令部分长度(2 字节明文 + 16 字节 GCM Tag);
  • Nonce:随机数,用于 AEAD 加密;
  • AHeader:加密后的指令部分;
  • 数据部分:实际传输的数据。

为了保持和原有协议、配置兼容,必须要通过且仅通过最开始 16 个字节计算出客户端的 UUID 信息。

EAuID(加密认证 ID)

EAuID 明文格式:

8 字节4 字节4 字节
TimestampRandCRC

其中:

  • Timestamp:以秒为单位的 64 位 Unix 时间戳(Big-Endian);
  • Rand:随机数;
  • CRC:CRC32([Timestamp, Rand]),使用 IEEE 多项式;

EAuID 加密:

  • 加密密钥:KDF(CmdKey, "AES Auth ID Encryption")[:16]
  • 加密方式:AES-128 块加密

ALength(加密长度)

ALength 是经过 AES-128-GCM 加密的 16 位大端序 VMess 包头部长度。

  • 加密密钥:KDF(CmdKey, "VMess Header AEAD Key_Length", EAuID, Nonce)[:16]
  • 不重数(Nonce):KDF(CmdKey, "VMess Header AEAD Nonce_Length", EAuID, Nonce)[:12]
  • 附加数据(AD):EAuID

AHeader(加密首部)

AHeader 是经过 AES-128-GCM 加密的 VMess 标准首部(即下文的指令部分内容)。

  • 加密密钥:KDF(CmdKey, "VMess Header AEAD Key", EAuID, Nonce)[:16]
  • 不重数(Nonce):KDF(CmdKey, "VMess Header AEAD Nonce", EAuID, Nonce)[:12]
  • 附加数据(AD):EAuID

AES-GCM 要求 Key-Nonce 组合不重复。由于其中使用了 EAuID、Nonce 作为 Key 和 Nonce 的 KDF 输入,有 96 位随机熵输入不会重复。因为使用用户 UUID 作为输入无法被攻击者解密。

MD5 认证格式(弃用)

16 字节X 字节余下部分
认证信息指令部分数据部分

其中:

  • 认证信息:HMAC-MD5 哈希值,用于验证客户端身份;
  • 指令部分:AES-128-CFB 加密的指令;
  • 数据部分:实际传输的数据。

认证信息

认证信息是一个 16 字节的哈希(hash)值,它的计算方式如下:

  • H = MD5
  • K = 用户 ID (16 字节)
  • M = UTC 时间,精确到秒,取值为当前时间的前后 30 秒随机值 (8 字节, Big-Endian)
  • Hash = HMAC(H, K, M)

指令部分加密

指令部分经过 AES-128-CFB 加密:

  • Key:MD5(用户 ID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
  • IV:MD5(X + X + X + X),X = []byte(认证信息生成的时间) (8 字节, Big-Endian)

指令部分(通用)

指令部分的结构对于 AEAD 和 MD5 认证方式是相同的,区别仅在于加密方式:

  • AEAD 认证:作为 AHeader 的明文,使用 AES-128-GCM 加密;
  • MD5 认证:使用 AES-128-CFB 加密。
1 字节16 字节16 字节1 字节1 字节4 位4 位1 字节1 字节2 字节1 字节N 字节P 字节4 字节
版本号 Ver请求加密 IV请求加密 Key响应认证 V选项 Opt余量 P加密方式 Sec保留指令 Cmd端口 Port地址类型 T地址 A随机值校验 F

选项 Opt 细节:(当某一位为 1 时,表示该选项启用)

01234567
XXXAPMRS

其中:

  • 版本号 Ver:始终为 1;
  • 请求加密 IV:随机值;
  • 请求加密 Key:随机值;
  • 响应认证 V:随机值;
  • 选项 Opt:
    • S (0x01):标准格式的数据流(默认开启);
    • R (0x02):客户端期待重用 TCP 连接(V2Ray 2.23+ 弃用);
      • 只有当 S 开启时,这一项才有效;
    • M (0x04):开启元数据混淆(建议开启);
      • 只有当 S 开启时,这一项才有效;
      • 当其项开启时,客户端和服务器端需要分别构造两个 Shake 实例,分别为 RequestMask = Shake(请求加密 IV), ResponseMask = Shake(响应加密 IV)。
    • P (0x08): 请求全局填充;
      • 只有当 M 开启时,这一项才有效;
      • 数据部分的加密方式只能是 AES-128-GCM 或 ChaCha20-Poly1305;
      • 当其项开启时,客户端和服务器端会根据上文提到的 Shake 实例生成随机长度的填充字节,并附在密文之后;
    • A (0x10):启用认证的数据包长度实验;
    • X:保留;
  • 余量 P:在校验值之前加入 P 字节的随机值;
  • 加密方式:指定数据部分的加密方式:
    • 0x01:Legacy (AES-128-CFB);
    • 0x03:AES-128-GCM;
    • 0x04:ChaCha20-Poly1305;
    • 0x05:None;
    • 0x06:Zero(无加密,无分块,裸流传输);
  • 指令 Cmd:
    • 0x01:TCP 数据;
    • 0x02:UDP 数据;
  • 端口 Port:Big Endian 格式的整型端口号;
  • 地址类型 T:
    • 0x01:IPv4;
    • 0x02:域名;
    • 0x03:IPv6;
  • 地址 A:
    • 当 T = 0x01 时,A 为 4 字节 IPv4 地址;
    • 当 T = 0x02 时,A 为 1 字节长度(L) + L 字节域名;
    • 当 T = 0x03 时,A 为 16 字节 IPv6 地址;
  • 校验 F:指令部分除 F 外所有内容的 FNV1a hash (Big-Endian);

数据部分(通用)

数据部分用于传输实际的请求/响应数据,有两种格式:

  • 基本格式:直接传输数据,已弃用;
  • 标准格式:将数据分块传输,支持更好的加密和完整性校验(默认)。

基本格式 (弃用)

此格式仅作为向后兼容所用,在之后的版本中可能被删除。

所有数据均认为是请求的实际内容。这些内容将被发往指令部分所指定的地址。当 Cmd = 0x01 时,这些数据将以 TCP 的形式发送;当 Cmd = 0x02 时,这些数据将以 UDP 形式发送。

此格式支持 None 和 AES-128-CFB 两种加密方式,加密的 Key 和 IV 由指令部分指定。

Zero 加密方式

当加密方式为 Zero (0x06) 时,会强制使用基本格式(而非标准格式),并且不进行任何加密。具体行为为:

  • 加密方式被视为 None;
  • Opt(S)(标准格式)被强制关闭;
  • Opt(M)(元数据混淆)被强制关闭;

这意味着数据部分将作为完全不加密的裸流进行传输。请注意,协议头部仍然会按照 AEAD 或 MD5 方式进行加密认证。

标准格式

当 Opt(S) 开启时,数据部分使用此格式。实际的请求数据被分割为若干个小块,每个小块的格式如下。服务器校验完所有的小块之后,再按基本格式的方式进行转发。

2 字节L-P 字节P 字节
长度 L数据包随机值

其中:

  • 长度 L:
    • Big Endian 格式的整型,最大值为 2^14;
    • 当 Opt(M) 关闭时,L 的实际值 = 数据值;
    • 当 Opt(M) 开启时,L 的实际值 = 数据值 xor Mask。Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte();
  • 填充长度 P:
    • 当 Opt(P) 关闭时,P = 0,即无填充字节;
    • 当 Opt(P) 开启时,P = ((RequestMask.NextByte() << 8) + RequestMask.NextByte()) % 64;
  • 数据包:由指定的加密方式加密过的数据包;

在传输结束之前,数据包中必须有实际数据,即除了长度和认证数据之外的数据。当传输结束时,客户端必须发送一个空的数据包,即 L = 0(不加密)或认证数据长度(有加密),来表示传输结束。

设数据包长度为 R = L - P,按加密方式不同,数据包的格式如下:

  • 不加密:
    • R 字节:实际数据;
  • AES-128-CFB:整个数据部分使用 AES-128-CFB 加密
    • 4 字节:实际数据的 FNV1a hash (Big-Endian);
    • R - 4 字节:实际数据;
  • AES-128-GCM:Key 为指令部分的 Key,IV = count (2 字节) + IV (10 字节)。count 从 0 开始递增,每个数据包加 1;IV 为 指令部分 IV 的第 3 至第 12 字节。
    • R - 16 字节:实际数据;
    • 16 字节:GCM 认证信息;
  • ChaCha20-Poly1305:Key = MD5(指令部分 Key) + MD5(MD5(指令部分 Key)),IV = count (2 字节) + IV (10 字节)。count 从 0 开始递增,每个数据包加 1;IV 为 指令部分 IV 的第 3 至第 12 字节。
    • R - 16 字节:实际数据;
    • 16 字节:Poly1305 认证信息;

服务器应答

服务器应答的格式与客户端请求相对应。服务器会根据客户端请求的认证方式(AEAD 或 MD5)使用对应的响应格式。

AEAD 认证响应

AEAD 认证模式下,响应加密 Key 和 IV 的派生方式:

  • 响应加密 Key:SHA256(请求加密 Key)[:16]
  • 响应加密 IV:SHA256(请求加密 IV)[:16]

应答头部数据使用 AES-128-GCM 加密,分为长度和内容两部分。

响应长度加密(2 字节长度 + 16 字节 GCM Tag):

  • 加密密钥:KDF(响应加密 Key, "AEAD Resp Header Len Key")[:16]
  • 不重数(Nonce):KDF(响应加密 IV, "AEAD Resp Header Len IV")[:12]
  • 附加数据(AD):无(nil)

响应内容加密:

  • 加密密钥:KDF(响应加密 Key, "AEAD Resp Header Key")[:16]
  • 不重数(Nonce):KDF(响应加密 IV, "AEAD Resp Header IV")[:12]
  • 附加数据(AD):无(nil)

MD5 认证响应(弃用)

MD5 认证模式下,响应加密 Key 和 IV 的派生方式:

  • 响应加密 Key:MD5(请求加密 Key)
  • 响应加密 IV:MD5(请求加密 IV)

应答头部数据使用 AES-128-CFB 加密。

响应头部格式

实际应答数据视加密设置不同而不同。

1 字节1 字节1 字节1 字节M 字节余下部分
响应认证 V选项 Opt指令 Cmd指令长度 M指令内容实际应答数据

其中:

  • 响应认证 V:必须和客户端请求中的响应认证 V 一致;
  • 选项 Opt:
    • 0x01:服务器端准备重用 TCP 连接(V2Ray 2.23+ 弃用);
  • 指令 Cmd:
    • 0x01:动态端口指令;
  • 实际应答数据:
    • 如果请求中的 Opt(S) 开启,则使用标准格式,否则使用基本格式;
    • 如果请求中的 Opt(M) 开启,则开启元数据混淆,Shake 实例为 ResponseMask;
    • 如果请求中的 Opt(P) 开启,则请求全局填充,Shake 实例为 ResponseMask;
    • 格式均和请求数据相同;

动态端口指令

1 字节2 字节16 字节2 字节1 字节1 字节
保留端口 Port用户 IDAlterID用户等级有效时间 T

其中:

  • 端口 Port:Big Endian 格式的整型端口号;
  • 有效时间 T:分钟数;

客户端在收到动态端口指令时,服务器已开放新的端口用于通信,这时客户端可以将数据发往新的端口。在 T 分钟之后,这个端口将失效,客户端必须重新使用主端口进行通信。

注释

  • 为确保向前兼容性,所有保留字段的值必须为 0。
在 GitHub 上编辑此页
上次更新:
贡献者: kslr, Kslr, Kid, Shelikhoo, FantasqueX, Konano, Kaede Akino
Next
mKCP 协议