# 🎭 REALITY: как прокси прикрывается настоящим чужим сайтом > [!info] О чём заметка > Подробный разбор протокола **REALITY** — замены TLS в [[xray/project-x|Xray-core]], которая позволяет прокси-серверу маскироваться под настоящий сторонний сайт без собственного домена и сертификата. Здесь — что происходит на уровне рукопожатия, как устроена криптографическая аутентификация и почему активное зондирование (active probing) не может отличить REALITY-сервер от реального сайта. Разбор основан на чтении исходного кода: серверная часть — форк `crypto/tls` в [github.com/XTLS/REALITY](https://github.com/XTLS/REALITY), клиентская — `transport/internet/reality/reality.go` в Xray-core. REALITY — это отдельный слой (security), не путать с [[xray/xtls-vision|XTLS/Vision]] (flow) и [[xray/vless|VLESS]] (протокол). ## TL;DR - **REALITY** — замена обычного серверного TLS. Владельцу прокси **не нужен свой домен и сертификат**: сервер маскируется под чужой публичный сайт (например крупный домен). - При рукопожатии сервер **физически проксирует ClientHello клиента на настоящий сайт** и возвращает его настоящий ответ. Для чужого наблюдателя сервер неотличим от порт-форварда на этот сайт. - «Свой» клиент опознаётся по криптографической метке, спрятанной в поле `SessionId` внутри ClientHello. Метка шифруется AES-GCM на общем секрете (X25519 ECDH) и невидима без серверного ключа. - Своему клиенту сервер выдаёт **временный сертификат, «подписанный» HMAC на общем секрете** — только владелец правильного ключа может его проверить. Чужой клиент/зонд получает настоящий сертификат реального сайта. - Итог: нет серверного TLS-отпечатка, есть защита от active probing, не нужен свой домен. Криптографически REALITY на 2026 год не вскрыт — DPI борется с ним поведенчески (см. [[VLESS/dpi-tls-june-2026|разбор июня 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|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](https://github.com/XTLS/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`): ```json { "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`): ```json { "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|заметке про схему ограничений июня 2026]]. ## 📚 См. также - [[xray/vless|Протокол VLESS]] — протокол, поверх которого работает REALITY - [[xray/xtls-vision|XTLS и Vision]] — flow-режим, который обычно идёт в паре с REALITY - [[xray/project-x|Project X (Xray-core)]] — обзор проекта и всех технологий - [[xray/xhttp|XHTTP]] — альтернативный транспорт, тоже умеет работать поверх REALITY - [[xray/clients-and-routing|Клиенты и маршрутизация]] — как подключиться к VLESS+REALITY-серверу - [[VLESS/dpi-tls-june-2026|DPI TLS heuristics 2026]] — поведенческий детект VLESS+REALITY - 🔗 [github.com/XTLS/REALITY](https://github.com/XTLS/REALITY) — исходный код протокола - 🔗 [Официальная дока: RealityObject](https://xtls.github.io/ru/config/transports/reality.html) — точные имена и значения полей - 🔗 [Руководство VLESS TCP REALITY (discussions #3518)](https://github.com/XTLS/Xray-core/discussions/3518) — подробный неофициальный разбор механики --- > [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ > При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны на GitHub: [исходник этой заметки](https://github.com/youtubediscord/todo/blob/main/xray/reality.md) · [весь репозиторий](https://github.com/youtubediscord/todo/tree/main).