Добавить новый раздел о стандартах FIDO, статьи о VLESS Encryption и слоях VLESS, а также несжатую иллюстрацию диагностики MTProxy. Обновить связанные материалы и направить ссылки на исходники новых статей в Forgejo.
30 KiB
| date | tags | aliases | link | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-07-17 |
|
|
https://github.com/XTLS/REALITY |
🎭 REALITY: как прокси прикрывается настоящим чужим сайтом
[!info] О чём заметка Подробный разбор протокола REALITY — замены TLS в xray/project-x, которая позволяет прокси-серверу маскироваться под настоящий сторонний сайт без собственного домена и сертификата. Здесь — что происходит на уровне рукопожатия, как устроена криптографическая аутентификация и почему активное зондирование (active probing) не может отличить REALITY-сервер от реального сайта. Разбор основан на чтении исходного кода: серверная часть — форк
crypto/tlsв github.com/XTLS/REALITY, клиентская —transport/internet/reality/reality.goв Xray-core. REALITY — это отдельный слой (security), не путать с xray/xtls-vision (flow) и xray/vless (протокол).
TL;DR
- REALITY — замена обычного серверного TLS. Владельцу прокси не нужен свой домен и сертификат: сервер маскируется под чужой публичный сайт (например крупный домен).
- При рукопожатии сервер физически проксирует ClientHello клиента на настоящий сайт и возвращает его настоящий ответ. Для чужого наблюдателя сервер неотличим от порт-форварда на этот сайт.
- «Свой» клиент опознаётся по криптографической метке, спрятанной в поле
SessionIdвнутри ClientHello. Метка шифруется AES-GCM на общем секрете (X25519 ECDH) и невидима без серверного ключа. - Своему клиенту сервер выдаёт временный сертификат, «подписанный» HMAC на общем секрете — только владелец правильного ключа может его проверить. Чужой клиент/зонд получает настоящий сертификат реального сайта.
- Итог: нет серверного TLS-отпечатка, есть защита от active probing, не нужен свой домен. Криптографически REALITY на 2026 год не вскрыт — DPI борется с ним поведенчески (см. VLESS/dpi-tls-june-2026).
Проблема, которую решает REALITY
До REALITY, чтобы прокси маскировался под HTTPS, владельцу нужен был собственный домен и настоящий TLS-сертификат (например от Let's Encrypt). Но это само по себе создавало сигнал для цензора: свежезарегистрированный домен, на который вдруг идёт много странного трафика с одного IP, — подозрительно. Плюс сервер терминировал TLS сам, а значит имел собственный TLS-отпечаток (fingerprint), который можно было каталогизировать и блокировать.
REALITY убирает и то, и другое: не нужен свой домен, и у сервера нет своего TLS-отпечатка, потому что настоящий ответ рукопожатия генерирует не он, а реальный чужой сайт, под который он маскируется.
Как работает рукопожатие: зеркалирование на реальный сайт
Это самая необычная часть REALITY. Точка входа на сервере — функция Server() в tls.go. Как только приходит клиентское соединение, сервер сразу дозванивается до реального сайта (адрес из поля target, например example.com:443) и оборачивает клиентское соединение в специальный MirrorConn.
MirrorConn работает как зеркало: каждый байт, который читается от клиента (включая его ClientHello), немедленно копируется на настоящий сайт. То есть клиентское рукопожатие физически уходит на реальный сервер example.com, и от него приходит настоящий ServerHello. Важно: перехватывается только ServerHello — та часть рукопожатия, которая в TLS 1.3 идёт открытым текстом, — вместе с длинами и таймингами последующих (уже зашифрованных) сообщений. Всё после ServerHello в TLS 1.3 зашифровано, и сервер REALITY туда не лезет.
Проще говоря: REALITY-сервер сначала ведёт себя как «прокладка» между клиентом и настоящим сайтом — просто перекидывает трафик туда-сюда. И только если он узнаёт в клиенте «своего» (по секретной метке), он перехватывает управление. Если не узнаёт — соединение так и остаётся сквозным проксированием на реальный сайт, и клиент честно говорит с example.com.
Сервер берётся за аутентификацию только при двух условиях: соединение по TLS 1.3 и SNI из ClientHello входит в список ServerNames (разрешённых сайтов). Иначе — чистый проброс.
Криптография: как опознаётся «свой» клиент
Метка «я свой» прячется в поле SessionId внутри ClientHello — обычно там случайные 32 байта, и REALITY-метка от случайного мусора неотличима без ключа.
Пошагово (клиент — UClient в reality.go):
- Общий секрет. Клиент считает
AuthKeyчерез X25519 ECDH: перемножает свой эфемерный ключ из ClientHello с публичным ключом сервера (в конфиге клиента это полеpassword). Затем прогоняет результат через HKDF (соль — первые 20 байтClientHello.Random, метка"REALITY"). Сервер вычисляет ровно тот же секрет из своего приватного ключа — классический Диффи-Хеллман. - Метаданные в SessionId. Клиент кладёт в 32 байта версию Xray, текущее время (для защиты от повторов) и свой
ShortId(8-байтный идентификатор, различающий клиентов). - Шифрование метки. Всё это шифруется AES-GCM на
AuthKey, причём в качестве дополнительных данных (AAD) берётся весьClientHello. Результат кладётся обратно вSessionId. БезAuthKeyметку нельзя ни прочитать, ни подделать.
Сервер делает всё зеркально: вычисляет тот же AuthKey, расшифровывает SessionId, достаёт версию/время/ShortId и проверяет:
- версия клиента в допустимом окне (
MinClientVer/MaxClientVer); - время в пределах
MaxTimeDiff(защита от воспроизведения старого перехваченного ClientHello); ShortIdесть в списке разрешённых (ShortIds).
Если всё сошлось — клиент признан «своим», и сервер перехватывает рукопожатие. Если нет — соединение остаётся проброшенным на реальный сайт.
Временный сертификат «на лету»
Когда клиент опознан как свой, сервер должен отдать ему сертификат — но не может использовать настоящий сертификат example.com (у него нет приватного ключа этого домена). Решение хитрое: сервер берёт заранее заготовленный самоподписанный ed25519-сертификат и подменяет его подпись на HMAC-SHA512, вычисленный на AuthKey.
Обычной проверки цепочки сертификатов у клиента нет (она отключена). Вместо неё клиент повторяет тот же HMAC на своём AuthKey и сверяет с подписью в сертификате. Совпало — значит сертификат выдан именно REALITY-сервером, знающим общий секрет, а не реальным сайтом и не MITM-посредником. Только владелец правильного ключа способен воспроизвести этот HMAC.
[!important] REALITY НЕ «ворует сертификаты» — распространённое заблуждение Частый миф: будто REALITY «крадёт» или «подменяет» настоящий сертификат целевого сайта. Это неверно, и официальное руководство XTLS это прямо опровергает. В TLS 1.3 сертификат сервера зашифрован (идёт после ServerHello), поэтому посредник его в принципе не видит, а сервер REALITY не может его ни прочитать, ни переслать. Что происходит на самом деле: REALITY реализует полноценный TLS 1.3, перехватывает у целевого сайта только открытый ServerHello (его длину, тайминги, внешний вид) и заменяет
key_share, а «своему» клиенту генерирует собственный временный доверенный сертификат (подписанный HMAC на общем секрете, см. выше). Настоящий сертификат целевого сайта легитимный клиент в нормальных условиях не получает вообще — он достаётся только чужому клиенту/зонду, которому идёт честный проброс на реальный сайт.
[!note] Три исхода для клиента Клиент REALITY различает три случая по полученному сертификату: временный доверенный (HMAC сошёлся) → прокси работает; настоящий сертификат реального сайта (значит сервер не опознал клиента или это редирект) → клиент уходит в режим «паука» — эмулирует обычный браузинг по сайту, чтобы даже при неудаче выглядеть как реальный посетитель; невалидный сертификат → TLS-ошибка и разрыв. Стартовый путь для режима паука задаётся параметром
spiderX.
Защита от active probing
Active probing — это когда цензор сам отправляет запрос на подозрительный сервер, чтобы проверить, прокси это или нет. REALITY здесь практически неуязвим, и вот почему.
Любой ClientHello без валидной REALITY-метки (не тот SNI, неизвестный ShortId, неверный тег AES-GCM, устаревшее время) обрабатывается как чистый проброс на реальный сайт: зонд отправил ClientHello — тот уже физически ушёл на example.com через MirrorConn, и ответ реального сайта вернулся зонду байт-в-байт. Зонд общается с настоящим example.com: настоящий сертификат, настоящий TLS 1.3, настоящее поведение сайта.
Ключевой момент: сервер REALITY не генерирует поддельный ServerHello — он использует тот, что реально пришёл от целевого сайта. Поэтому нет никакого «своего» ответа, который зонд мог бы спровоцировать и опознать. А сама метка спрятана в SessionId и неотличима от случайного session id обычного TLS 1.3-клиента.
Рекомендация из документации REALITY: форвардить на целевой сайт ещё и TCP/80 и UDP/443, чтобы сервер выглядел как обычный порт-форвард на этот сайт со всех сторон.
uTLS: правдоподобный ClientHello
Чтобы сам ClientHello «своего» клиента не выделялся, REALITY использует библиотеку uTLS (refraction-networking/utls) — она собирает ClientHello с точным набором расширений, шифров, GREASE и порядком полей как у настоящего браузера. Браузер задаётся параметром fingerprint (по умолчанию chrome). REALITY встраивает свою метку поверх уже собранного uTLS-рукопожатия, правя только SessionId и не ломая отпечаток.
Обязательное условие: выбранный fingerprint должен поддерживать TLS 1.3 — иначе не будет эфемерного ECDHE-ключа, из которого выводится AuthKey, и рукопожатие невозможно.
Почему REALITY работает только по TCP (не по QUIC)
Для REALITY рекомендуется единственный надёжный транспорт — RAW (TCP). Причина техническая: REALITY прячет свою метку в поле SessionId внутри ClientHello. В обычном TCP-TLS это поле почти всегда заполняется случайными 32 байтами (для совместимости), поэтому REALITY-метка в нём неотличима от случайного мусора — идеальное место, чтобы спрятаться.
А в QUIC (на котором работает HTTP/3) поле SessionId в TLS имеет нулевую длину и модифицировать его нельзя. Некуда встроить метку — значит REALITY поверх QUIC не работает в принципе. Поэтому связка VLESS+REALITY живёт по TCP, и именно поэтому xray/xtls-vision тоже TCP-only.
Как выбрать target и serverNames
Выбор целевого сайта — самая ответственная часть настройки, от неё зависит и скрытность, и скорость. Требования к сайту-камуфляжу:
- Зарубежный сайт (с точки зрения вашей страны) с поддержкой TLS 1.3 и HTTP/2.
- Домен, который не редиректит куда-то ещё (редирект
domain→www.domainдопустим). - Желательно IP-адрес, близкий к вашему серверу по сетевому расстоянию (меньше задержка, менее подозрительно).
[!danger] Не используйте крупные сайты (Google, CDN-гиганты) как target Многие крупные сайты имеют CDN-серверы внутри страны. Если у сайта есть локальные серверы, то с точки зрения цензора вы в норме должны обращаться к внутренним адресам, а не выходить за границу — обращение за рубеж к «домашнему» сайту само становится аномалией.
dl.google.comв примерах официального репозитория приведён только для иллюстрации — реально Google в качестве target не берут. Плюс: если target за CDN (например Cloudflare), непрошедший трафик пробрасывается туда, и ваш сервер превращается в бесплатный порт-форвард к чужому CDN.
Практично: если своего домена нет — просканируйте соседние по ASN домены инструментом RealiTLScanner. Если свой домен есть — используйте его (лучшая практика — сертификат из той же ASN, что и ваш сервер). Дополнительно можно фильтровать нежелательные SNI через Nginx.
Пост-квантовая защита
REALITY готов к эпохе квантовых компьютеров на двух уровнях:
- Обмен ключами
X25519MLKEM768. Если целевой сайт поддерживает этот гибридный пост-квантовый алгоритм, клиент REALITY применяет его автоматически. Проверить поддержку у target:xray tls ping cloudflare.com(подставьте свой адрес). - Подпись
ML-DSA-65(опционально). Задаётся паройmldsa65Seed(сервер) /mldsa65Verify(клиент), генерируется командойxray mldsa65. Добавляет к временному сертификату пост-квантовую подпись: даже если в будущем взломают X25519 и утечётpassword, MITM-атака останется невозможной. На старые клиенты без поддержки не влияет.
[!warning] Условие для ML-DSA-65: крупный сертификат target Пост-квантовая подпись увеличивает размер временного сертификата REALITY. Чтобы это не создавало отличительную особенность, сертификат самого целевого сайта тоже должен быть большим — свыше 3500 байт. Для этого выбирайте target с RSA-сертификатом (не ECDSA — у него длина меньше) и поддержкой
X25519MLKEM768. Размер проверяется командойxray tls ping example.com.
Минимальный конфиг
Пример боевого минимума (имена/ключи здесь условные, домен example.com — плейсхолдер вашего реального target). Обратите внимание: у сервера — target/privateKey/shortIds, у клиента — serverName/password/shortId, и с обеих сторон flow: xtls-rprx-vision.
Сервер (inbound):
{
"protocol": "vless",
"settings": {
"clients": [{ "id": "<UUID>", "flow": "xtls-rprx-vision" }],
"decryption": "none"
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"target": "example.com:443",
"serverNames": ["example.com", "www.example.com"],
"privateKey": "<приватный ключ из xray x25519>",
"shortIds": ["6ba85179e30d4fc2"]
}
}
}
Клиент (outbound):
{
"protocol": "vless",
"settings": {
"vnext": [{
"address": "<IP вашего сервера>",
"port": 443,
"users": [{ "id": "<тот же UUID>", "encryption": "none", "flow": "xtls-rprx-vision" }]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.example.com",
"password": "<публичный ключ сервера>",
"shortId": "6ba85179e30d4fc2"
}
}
}
[!note] target как fallback
target— это по сути точка fallback: весь трафик, не прошедший REALITY-проверку (чужой клиент, зонд), Xray напрямую пересылает туда. Можно указать не только внешний сайт, но и локальный фейковый веб-сервер — например"target": "localhost:10433", где на порту 10433 крутится подставной сайт. Тогда зонд увидит именно его. Если же оставитьtargetна реальный внешний сайт — непрошедший трафик пойдёт туда.
Ключевые параметры конфига
| Параметр | Сторона | Что это |
|---|---|---|
target |
сервер | Адрес реального сайта для маскировки и проброса (example.com:443). По наличию этого поля ядро отличает серверный конфиг от клиентского. Старое имя — dest (сейчас оба поля — алиасы) |
serverNames |
сервер | Список разрешённых SNI; клиентский SNI должен точно совпасть (wildcard не поддерживается) |
privateKey |
сервер | Приватный ключ X25519 (генерируется xray x25519) |
password |
клиент | Публичный ключ X25519 сервера. Раньше поле называлось publicKey, переименовано во избежание путаницы |
shortIds / shortId |
сервер / клиент | Список разрешённых 8-байтных идентификаторов клиентов (openssl rand -hex 8); пустое значение допускает пустой shortId |
maxTimeDiff |
сервер | Допустимый разброс времени клиента в миллисекундах (защита от replay); 0 — выключено |
minClientVer / maxClientVer |
сервер | Окно допустимых версий Xray у клиента (например 1.8.6) |
fingerprint |
клиент | uTLS-отпечаток браузера (по умолчанию chrome) |
serverName |
клиент | SNI, который клиент отправляет (из серверного serverNames) |
spiderX |
клиент | Стартовый путь режима «паука» при неудаче (например /blog) |
xver |
сервер | Версия PROXY protocol (0/1/2) для передачи реального IP клиента на target; по умолчанию 0 |
mldsa65Seed / mldsa65Verify |
сервер / клиент | Опциональная пост-квантовая подпись ML-DSA-65 (см. ниже) |
[!warning] Про лимит fallback-трафика (
limitFallbackUpload/limitFallbackDownload) Эти параметры троттлят скорость проброса непрошедшего проверку трафика на реальный сайт (алгоритм «маркерная корзина»:afterBytes— после скольки байт включается лимит,bytesPerSec— базовая скорость,burstBytesPerSec— пиковая). Нужны в основном чтобы сервер не превратился в бесплатный порт-форвард к чужому CDN. Но официальная дока прямо предупреждает: включённый лимит создаёт детектируемый паттерн. Если и включать — рандомизируйте значения (особенно в GUI/панелях/скриптах установки), иначе фиксированный лимит сам станет сигнатурой. По умолчанию лучше не трогать, а вместо этого использовать сертификат из той же ASN или свой домен.
Отличие от обычного TLS и от ShadowTLS
От обычного TLS: не нужен свой домен и сертификат; настоящий ServerHello генерирует целевой сайт, а не сервер (нет серверного отпечатка); обычная валидация цепочки заменена на HMAC-проверку на общем секрете.
От ShadowTLS: оба проксируют рукопожатие к реальному сайту, но ShadowTLS перехватывает соединение после рукопожатия и продолжает своим потоком (характерный признак — hijack после ServerHello). REALITY же зеркалит ClientHello на настоящий сайт, берёт от него открытый ServerHello (с его длинами и таймингами) и генерирует своему клиенту собственный временный сертификат; для чужого клиента соединение остаётся буквально сквозным TLS реального сайта.
[!warning] Что REALITY НЕ защищает Криптографически REALITY на июль 2026 не вскрыт — метка неотличима без ключа, active probing бессилен. Но это не значит, что связку нельзя заблокировать: современный DPI перешёл на поведенческий анализ (тайминги, объёмы, эвристики соединения), который не трогает саму криптографию REALITY, но ловит прокси по косвенным признакам. Подробный разбор — в VLESS/dpi-tls-june-2026.
📚 См. также
- xray/vless — протокол, поверх которого работает REALITY
- xray/xtls-vision — flow-режим, который обычно идёт в паре с REALITY
- xray/vless-encryption — внутреннее шифрование протокола: чем оно отличается от маскировки REALITY и когда нужно вдобавок к ней
- xray/vless-stack-map — матрица совместимости всех пяти осей конфига
- xray/project-x — обзор проекта и всех технологий
- xray/xhttp — альтернативный транспорт, тоже умеет работать поверх REALITY
- xray/clients-and-routing — как подключиться к VLESS+REALITY-серверу
- VLESS/dpi-tls-june-2026 — поведенческий детект VLESS+REALITY
- 🔗 github.com/XTLS/REALITY — исходный код протокола
- 🔗 Официальная дока: RealityObject — точные имена и значения полей
- 🔗 Руководство VLESS TCP REALITY (discussions #3518) — подробный неофициальный разбор механики
[!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: исходник этой заметки · весь репозиторий.