Nikita Korotaev
2 months ago
committed by
GitHub
9 changed files with 523 additions and 499 deletions
@ -1,175 +1,178 @@
|
||||
# VMess 协议 |
||||
# Протокол VMess |
||||
|
||||
VMess 是一个加密传输协议,可以作为 Xray 客户端和服务器之间的桥梁。 |
||||
VMess - это зашифрованный транспортный протокол, который может служить мостом между клиентом и сервером Xray. |
||||
|
||||
## 版本 |
||||
## Версия |
||||
|
||||
当前版本号为 1。 |
||||
Текущая версия протокола - 1. |
||||
|
||||
## 依赖 |
||||
## Зависимости |
||||
|
||||
### 底层协议 |
||||
### Базовый протокол |
||||
|
||||
VMess 是一个基于 TCP 的协议,所有数据使用 TCP 传输。 |
||||
VMess - это протокол, основанный на TCP, все данные передаются по TCP. |
||||
|
||||
### 用户 ID |
||||
### Идентификатор пользователя |
||||
|
||||
ID 等价于 [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier),是一个 16 字节长的随机数,它的作用相当于一个令牌(Token)。 |
||||
一个 ID 形如:de305d54-75b4-431b-adb2-eb6b9e546014,几乎完全随机,可以使用任何的 UUID 生成器来生成,比如[这个](https://www.uuidgenerator.net/)。 |
||||
ID эквивалентен [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) - это 16-байтовое случайное число, которое действует как токен. |
||||
ID выглядит следующим образом: de305d54-75b4-431b-adb2-eb6b9e546014, он практически полностью случаен и может быть сгенерирован с помощью любого генератора UUID, например [этого](https://www.uuidgenerator.net/). |
||||
|
||||
用户 ID 可在[配置文件](../../config)中指定。 |
||||
Идентификатор пользователя можно указать в [файле конфигурации](../../config). |
||||
|
||||
### 函数 |
||||
### Функции |
||||
|
||||
- MD5: [MD5 函数](https://en.wikipedia.org/wiki/MD5) |
||||
- 输入参数为任意长度的 byte 数组 |
||||
- 输出为一个 16 byte 的数组 |
||||
- HMAC: [HMAC 函数](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) |
||||
- 输入参数为: |
||||
- H:散列函数 |
||||
- K:密钥,任意长度的 byte 数组 |
||||
- M:消息,任意长度的 byte 数组 |
||||
- Shake: [SHA3-Shake128 函数](https://en.wikipedia.org/wiki/SHA-3) |
||||
- 输入参数为任意长度的字符串 |
||||
- 输出为任意长度的字符串 |
||||
- MD5: функция [MD5](https://en.wikipedia.org/wiki/MD5) |
||||
- Входные данные: массив байтов произвольной длины |
||||
- Выходные данные: массив из 16 байтов |
||||
- HMAC: функция [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) |
||||
- Входные данные: |
||||
- H: хэш-функция |
||||
- K: ключ, массив байтов произвольной длины |
||||
- M: сообщение, массив байтов произвольной длины |
||||
- Shake: функция [SHA3-Shake128](https://en.wikipedia.org/wiki/SHA-3) |
||||
- Входные данные: строка произвольной длины |
||||
- Выходные данные: строка произвольной длины |
||||
|
||||
## 通讯过程 |
||||
## Процесс коммуникации |
||||
|
||||
VMess 是一个无状态协议,即客户端和服务器之间不需要握手即可直接传输数据,每一次数据传输对之前和之后的其它数据传输没有影响。 |
||||
VMess - это протокол без сохранения состояния, то есть клиент и сервер могут передавать данные напрямую без рукопожатия, и каждая передача данных не влияет на предыдущие или последующие передачи. |
||||
|
||||
VMess 的客户端发起一次请求,服务器判断该请求是否来自一个合法的客户端。如验证通过,则转发该请求,并把获得的响应发回给客户端。 |
||||
Клиент VMess отправляет запрос, а сервер проверяет, исходит ли этот запрос от легитимного клиента. Если проверка пройдена, сервер пересылает запрос и отправляет полученный ответ клиенту. |
||||
|
||||
VMess 使用非对称格式,即客户端发出的请求和服务器端的响应使用了不同的格式。 |
||||
VMess использует асимметричный формат, то есть запрос, отправляемый клиентом, и ответ сервера имеют разные форматы. |
||||
|
||||
## 客户端请求 |
||||
## Запрос клиента |
||||
|
||||
| 16 字节 | X 字节 | 余下部分 | |
||||
| -------- | -------- | -------- | |
||||
| 认证信息 | 指令部分 | 数据部分 | |
||||
| 16 байт | X байт | Оставшаяся часть | |
||||
|-------------------------------|------------------|------------------| |
||||
| Информация для аутентификации | Часть с командой | Часть с данными | |
||||
|
||||
### 认证信息 |
||||
### Информация для аутентификации |
||||
|
||||
认证信息是一个 16 字节的哈希(hash)值,它的计算方式如下: |
||||
Информация для аутентификации - это 16-байтовое хэш-значение, которое вычисляется следующим образом: |
||||
|
||||
- H = MD5 |
||||
- K = 用户 ID (16 字节) |
||||
- M = UTC 时间,精确到秒,取值为当前时间的前后 30 秒随机值(8 字节, Big Endian) |
||||
- K = идентификатор пользователя (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) |
||||
|
||||
| 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 时,表示该选项启用) |
||||
|
||||
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
||||
| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | |
||||
| X | X | X | X | X | M | R | S | |
||||
|
||||
其中: |
||||
|
||||
- 版本号 Ver:始终为 1; |
||||
- 数据加密 IV:随机值; |
||||
- 数据加密 Key:随机值; |
||||
- 响应认证 V:随机值; |
||||
- 选项 Opt: |
||||
- S (0x01):标准格式的数据流(建议开启); |
||||
- R (0x02):客户端期待重用 TCP 连接(Xray 2.23+ 弃用); |
||||
- 只有当 S 开启时,这一项才有效; |
||||
- M (0x04):开启元数据混淆(建议开启); |
||||
- 只有当 S 开启时,这一项才有效; |
||||
- 当其项开启时,客户端和服务器端需要分别构造两个 Shake 实例,分别为 RequestMask = Shake(请求数据 IV), ResponseMask = Shake(响应数据 IV)。 |
||||
- X:保留 |
||||
- 余量 P:在校验值之前加入 P 字节的随机值; |
||||
- 加密方式:指定数据部分的加密方式,可选的值有: |
||||
- 0x00:AES-128-CFB; |
||||
- 0x01:不加密; |
||||
- 0x02:AES-128-GCM; |
||||
- 0x03:ChaCha20-Poly1305; |
||||
- 指令 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; |
||||
|
||||
### 数据部分 |
||||
|
||||
当 Opt(S) 开启时,数据部分使用此格式。实际的请求数据被分割为若干个小块,每个小块的格式如下。服务器校验完所有的小块之后,再按基本格式的方式进行转发。 |
||||
|
||||
| 2 字节 | L 字节 | |
||||
| :----: | :----: | |
||||
| 长度 L | 数据包 | |
||||
|
||||
其中: |
||||
|
||||
- 长度 L:Big Endian 格式的整型,最大值为 2^14; |
||||
- 当 Opt(M) 开启时,L 的值 = 真实值 xor Mask。Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte(); |
||||
- 数据包:由指定的加密方式加密过的数据包; |
||||
|
||||
在传输结束之前,数据包中必须有实际数据,即除了长度和认证数据之外的数据。当传输结束时,客户端必须发送一个空的数据包,即 L = 0(不加密) 或认证数据长度(有加密),来表示传输结束。 |
||||
|
||||
按加密方式不同,数据包的格式如下: |
||||
|
||||
- 不加密: |
||||
- L 字节:实际数据; |
||||
- AES-128-CFB:整个数据部分使用 AES-128-CFB 加密 |
||||
- 4 字节:实际数据的 FNV1a hash; |
||||
- L - 4 字节:实际数据; |
||||
- AES-128-GCM:Key 为指令部分的 Key,IV = count (2 字节) + IV (10 字节)。count 从 0 开始递增,每个数据包加 1;IV 为 指令部分 IV 的第 3 至第 12 字节。 |
||||
- L - 16 字节:实际数据; |
||||
- 16 字节:GCM 认证信息 |
||||
- ChaCha20-Poly1305:Key = MD5(指令部分 Key) + MD5(MD5(指令部分 Key)),IV = count (2 字节) + IV (10 字节)。count 从 0 开始递增,每个数据包加 1;IV 为 指令部分 IV 的第 3 至第 12 字节。 |
||||
- L - 16 字节:实际数据; |
||||
- 16 字节:Poly1305 认证信息 |
||||
|
||||
## 服务器应答 |
||||
|
||||
应答头部数据使用 AES-128-CFB 加密,IV 为 MD5(数据加密 IV),Key 为 MD5(数据加密 Key)。实际应答数据视加密设置不同而不同。 |
||||
|
||||
| 1 字节 | 1 字节 | 1 字节 | 1 字节 | M 字节 | 余下部分 | |
||||
| ---------- | -------- | -------- | ---------- | -------- | ------------ | |
||||
| 响应认证 V | 选项 Opt | 指令 Cmd | 指令长度 M | 指令内容 | 实际应答数据 | |
||||
|
||||
其中: |
||||
|
||||
- 响应认证 V:必须和客户端请求中的响应认证 V 一致; |
||||
- 选项 Opt: |
||||
- 0x01:服务器端准备重用 TCP 连接(Xray 2.23+ 弃用); |
||||
- 指令 Cmd: |
||||
- 0x01:动态端口指令 |
||||
- 实际应答数据: |
||||
- 如果请求中的 Opt(S) 开启,则使用标准格式,否则使用基本格式。 |
||||
- 格式均和请求数据相同。 |
||||
- 当 Opt(M) 开启时,长度 L 的值 = 真实值 xor Mask。Mask = (ResponseMask.NextByte() << 8) + ResponseMask.NextByte(); |
||||
|
||||
### 动态端口指令 |
||||
|
||||
| 1 字节 | 2 字节 | 16 字节 | 2 字节 | 1 字节 | 1 字节 | |
||||
| ------ | --------- | ------- | ------- | -------- | ---------- | |
||||
| 保留 | 端口 Port | 用户 ID | AlterID | 用户等级 | 有效时间 T | |
||||
|
||||
其中: |
||||
|
||||
- 端口 Port:Big Endian 格式的整型端口号; |
||||
- 有效时间 T:分钟数; |
||||
|
||||
客户端在收到动态端口指令时,服务器已开放新的端口用于通信,这时客户端可以将数据发往新的端口。在 T 分钟之后,这个端口将失效,客户端必须重新使用主端口进行通信。 |
||||
|
||||
## 注释 |
||||
|
||||
- 为确保向前兼容性,所有保留字段的值必须为 0。 |
||||
### Часть с командой |
||||
|
||||
Часть с командой шифруется с помощью AES-128-CFB: |
||||
|
||||
- Ключ: MD5(идентификатор пользователя + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21')) |
||||
- Вектор инициализации: MD5(X + X + X + X), X = []byte(время генерации информации для аутентификации) (8 байт, Big Endian) |
||||
|
||||
| 1 байт | 16 байт | 16 байт | 1 байт | 1 байт | 4 бита | 4 бита | 1 байт | 1 байт | 2 байта | 1 байт | N байт | P байт | 4 байта | |
||||
|------------------|--------------------------------------------|----------------------------|-------------------------|-----------|-----------|----------------------|-----------------|-------------|-----------|--------------|---------|------------------|---------------------| |
||||
| Номер версии Ver | Вектор инициализации для шифрования данных | Ключ для шифрования данных | Аутентификация ответа V | Опция Opt | Остаток P | Метод шифрования Sec | Зарезервировано | Команда Cmd | Порт Port | Тип адреса T | Адрес A | Случайные данные | Контрольная сумма F | |
||||
|
||||
Подробности опции Opt: (если бит равен 1, опция включена) |
||||
|
||||
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
||||
|---|---|---|---|---|---|---|---| |
||||
| X | X | X | X | X | M | R | S | |
||||
|
||||
Где: |
||||
|
||||
- Номер версии Ver: всегда равен 1; |
||||
- Вектор инициализации для шифрования данных: случайное значение; |
||||
- Ключ для шифрования данных: случайное значение; |
||||
- Аутентификация ответа V: случайное значение; |
||||
- Опция Opt: |
||||
- S (0x01): стандартный формат потока данных (рекомендуется включать); |
||||
- R (0x02): клиент ожидает повторного использования TCP-соединения (устарело в Xray 2.23+); |
||||
- Действительна только при включенной опции S; |
||||
- M (0x04): включить обфускацию метаданных (рекомендуется включать); |
||||
- Действительна только при включенной опции S; |
||||
- Если эта опция включена, клиент и сервер должны создать два экземпляра Shake: RequestMask = Shake(вектор инициализации для шифрования данных запроса), ResponseMask = Shake(вектор инициализации для шифрования данных ответа). |
||||
- X: зарезервировано |
||||
- Остаток P: добавить P байт случайных данных перед контрольной суммой; |
||||
- Метод шифрования: указывает метод шифрования для части с данными, возможные значения: |
||||
- 0x00: AES-128-CFB; |
||||
- 0x01: без шифрования; |
||||
- 0x02: AES-128-GCM; |
||||
- 0x03: ChaCha20-Poly1305; |
||||
- Команда 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: хэш FNV1a всей части с командой, кроме F; |
||||
|
||||
### Часть с данными |
||||
|
||||
Если Opt(S) включена, для части с данными используется следующий формат. Фактические данные запроса разбиваются на несколько блоков, каждый из которых имеет следующий формат. После проверки всех блоков сервер пересылает их в соответствии с базовым форматом. |
||||
|
||||
| 2 байта | L байт | |
||||
|---------|--------------| |
||||
| Длина L | Пакет данных | |
||||
|
||||
Где: |
||||
|
||||
- Длина L: целое число в формате Big Endian, максимальное значение 2^14; |
||||
- Если Opt(M) включена, значение L = истинное значение xor Mask. Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte(); |
||||
- Пакет данных: пакет данных, зашифрованный указанным методом шифрования; |
||||
|
||||
До завершения передачи в пакете данных должны быть фактические данные, то есть данные, отличные от длины и данных аутентификации. При завершении передачи клиент должен отправить пустой пакет данных, то есть L = 0 (без шифрования) или длину данных аутентификации (с шифрованием), чтобы сигнализировать о завершении передачи. |
||||
|
||||
Формат пакета данных зависит от метода шифрования: |
||||
|
||||
- Без шифрования: |
||||
- L байт: фактические данные; |
||||
- AES-128-CFB: вся часть с данными шифруется с помощью AES-128-CFB |
||||
- 4 байта: хэш FNV1a фактических данных; |
||||
- L - 4 байта: фактические данные; |
||||
- AES-128-GCM: ключ - это ключ из части с командой, вектор инициализации = count (2 байта) + IV (10 байт). count начинается с 0 и увеличивается на 1 для каждого пакета данных; IV - это байты с 3 по 12 из вектора инициализации части с командой. |
||||
- L - 16 байт: фактические данные; |
||||
- 16 байт: данные аутентификации GCM |
||||
- ChaCha20-Poly1305: ключ = MD5(ключ из части с командой) + MD5(MD5(ключ из части с командой)), вектор инициализации = count (2 байта) + IV (10 байт). count начинается с 0 и увеличивается на 1 для каждого пакета данных; IV - это байты с 3 по 12 из вектора инициализации части с командой. |
||||
- L - 16 байт: фактические данные; |
||||
- 16 байт: данные аутентификации Poly1305 |
||||
|
||||
## Ответ сервера |
||||
|
||||
Данные заголовка ответа шифруются с помощью AES-128-CFB, вектор инициализации - MD5(вектор инициализации для шифрования данных), ключ - MD5(ключ для шифрования данных). Фактические данные ответа зависят от настроек шифрования. |
||||
|
||||
| 1 байт | 1 байт | 1 байт | 1 байт | M байт | Оставшаяся часть | |
||||
|-------------------------|-----------|-------------|-----------------|--------------------|---------------------------| |
||||
| Аутентификация ответа V | Опция Opt | Команда Cmd | Длина команды M | Содержимое команды | Фактические данные ответа | |
||||
|
||||
Где: |
||||
|
||||
- Аутентификация ответа V: должна совпадать с аутентификацией ответа V в запросе клиента; |
||||
- Опция Opt: |
||||
- 0x01: сервер готов повторно использовать TCP-соединение (устарело в Xray 2.23+); |
||||
- Команда Cmd: |
||||
- 0x01: команда динамического порта |
||||
- Фактические данные ответа: |
||||
- Если Opt(S) в запросе включена, используется стандартный формат, в противном случае используется базовый формат. |
||||
- Формат такой же, как и у данных запроса. |
||||
- Если Opt(M) включена, значение длины L = истинное значение xor Mask. Mask = (ResponseMask.NextByte() << 8) + ResponseMask.NextByte(); |
||||
|
||||
### Команда динамического порта |
||||
|
||||
| 1 байт | 2 байта | 16 байт | 2 байта | 1 байт | 1 байт | |
||||
|-----------------|-----------|----------------------------|---------|----------------------|------------------| |
||||
| Зарезервировано | Порт Port | Идентификатор пользователя | AlterID | Уровень пользователя | Время действия T | |
||||
|
||||
Где: |
||||
|
||||
- Порт Port: номер порта в формате Big Endian; |
||||
- Время действия T: количество минут; |
||||
|
||||
Когда клиент получает команду динамического порта, сервер уже открыл новый порт для связи, и клиент может отправлять данные на этот новый порт. Через T минут этот порт станет недействительным, и клиент должен будет снова использовать основной порт для связи. |
||||
|
||||
## Примечания |
||||
|
||||
- Для обеспечения обратной совместимости все зарезервированные поля должны иметь значение 0. |
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in new issue