# 🎭 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).