# Протокол VMess VMess - это зашифрованный транспортный протокол, который может служить мостом между клиентом и сервером Xray. ## Версия Текущая версия протокола - 1. ## Зависимости ### Базовый протокол VMess - это протокол, основанный на TCP, все данные передаются по TCP. ### Идентификатор пользователя ID эквивалентен [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) - это 16-байтовое случайное число, которое действует как токен. ID выглядит следующим образом: de305d54-75b4-431b-adb2-eb6b9e546014, он практически полностью случаен и может быть сгенерирован с помощью любого генератора UUID, например [этого](https://www.uuidgenerator.net/). Идентификатор пользователя можно указать в [файле конфигурации](../../config). ### Функции - 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 использует асимметричный формат, то есть запрос, отправляемый клиентом, и ответ сервера имеют разные форматы. ## Запрос клиента | 16 байт | X байт | Оставшаяся часть | |-------------------------------|------------------|------------------| | Информация для аутентификации | Часть с командой | Часть с данными | ### Информация для аутентификации Информация для аутентификации - это 16-байтовое хэш-значение, которое вычисляется следующим образом: - H = MD5 - K = идентификатор пользователя (16 байт) - M = время UTC с точностью до секунды, случайное значение в диапазоне ±30 секунд от текущего времени (8 байт, Big Endian) - Hash = HMAC(H, K, M) ### Часть с командой Часть с командой шифруется с помощью 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.