todo/xray/reality.md
loop-uh 482ef21393
All checks were successful
Published content check / validate (push) Successful in 3s
Сохранить локальные статьи о FIDO и VLESS
Добавить новый раздел о стандартах FIDO, статьи о VLESS Encryption и слоях VLESS, а также несжатую иллюстрацию диагностики MTProxy. Обновить связанные материалы и направить ссылки на исходники новых статей в Forgejo.
2026-08-07 15:52:30 +03:00

30 KiB
Raw Permalink Blame History

date tags aliases link
2026-07-17
xray
reality
tls
vless
censorship
anti-probing
REALITY
Xray REALITY
REALITY handshake
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):

  1. Общий секрет. Клиент считает AuthKey через X25519 ECDH: перемножает свой эфемерный ключ из ClientHello с публичным ключом сервера (в конфиге клиента это поле password). Затем прогоняет результат через HKDF (соль — первые 20 байт ClientHello.Random, метка "REALITY"). Сервер вычисляет ровно тот же секрет из своего приватного ключа — классический Диффи-Хеллман.
  2. Метаданные в SessionId. Клиент кладёт в 32 байта версию Xray, текущее время (для защиты от повторов) и свой ShortId (8-байтный идентификатор, различающий клиентов).
  3. Шифрование метки. Всё это шифруется 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.
  • Домен, который не редиректит куда-то ещё (редирект domainwww.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.

📚 См. также


[!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: исходник этой заметки · весь репозиторий.