CRYPTO Криптография и анатомия туннеля

Все VPN, от 1996 до 2026, построены на одной и той же триаде: обмен ключами → симметричное AEAD-шифрование → аутентификация. Понял триаду — все протоколы дальше укладываются за полчаса.

Что узнаешь

1. Симметрика vs асимметрика — самая базовая дихотомия

В криптографии есть ровно два типа шифров. Все системы — комбинация.

Симметричное vs асимметричное шифрование
① Симметричное (shared secret) Один и тот же ключ шифрует и расшифровывает. Быстро, ОЧЕНЬ быстро. Alice 🔑 = K шифрует ключом K «kHd9!@xQ» Bob 🔑 = K ⚠ Как Боб получил ключ K? Если по тому же каналу — взломают ② Асимметричное (public/private key) Два разных ключа: публичный шифрует, приватный расшифровывает. Медленнее в 100×. Alice 🔓 pub_Alice 🔐 priv_Alice шифрует pub_Bob «kHd9!@xQ» Bob 🔓 pub_Bob 🔐 priv_Bob ✓ Боб расшифровывает priv_Bob pub_Bob свободно раздаётся Решение: используем асимметрику ОДИН раз для обмена симметричным ключом, дальше шифруем симметрикой. Это и есть «гибридная» криптосистема. Так делают ВСЕ VPN.
Современные VPN — гибридные: асимметрика для key exchange, симметрика для трафика

Почему симметрика — быстро, асимметрика — медленно

AES-GCM на современном CPU с AES-NI шифрует 4-10 ГБ/с на одно ядро. ChaCha20-Poly1305 (ARM, мобильники) — 1-2 ГБ/с. RSA-2048 — ~1 МБ/с. X25519 (эллиптика) — единичные миллисекунды на handshake.

Поэтому ни один VPN не шифрует пакеты «асимметрикой» — это убило бы скорость. Асимметрика используется только в handshake (раз в час) — там нужно безопасно договориться.

2. AEAD — единственный правильный режим в 2026

Голое симметричное шифрование (AES в режиме CBC, ChaCha20 как stream-cipher) — недостаточно. Шифр только скрывает данные, но не защищает от подмены: атакующий может «перевернуть» бит, и расшифрованный текст изменится без обнаружения.

AEAD = Authenticated Encryption with Associated Data. Это режим, который к шифру добавляет MAC-тег (Message Authentication Code). При расшифровке проверяется тег — если данные подменили, тег не сойдётся и пакет отбрасывается.

AEAD: шифрование + аутентификация в одном
Plaintext «GET / HTTP/2» Ключ K 256 бит Nonce 96 бит, ↑ счётчик AEAD ChaCha20-Poly1305 или AES-GCM шифрование + MAC Ciphertext A3:4F:91:...:E2 Auth Tag (16 байт) «отпечаток» — проверка целостности Получатель: расшифровал → пересчитал tag → совпадает? Если нет — пакет ВЫБРАСЫВАЕТСЯ.
AEAD склеивает шифр и MAC в одну операцию — атомарно
Под капотом

Почему два AEAD-стандарта: AES-GCM и ChaCha20-Poly1305

AES-GCM — золотой стандарт. На современных x86 CPU есть аппаратная инструкция AES-NI (с 2010 года), которая делает AES в 5-10 раз быстрее обычного кода. На сервере AES-GCM выгоднее.

ChaCha20-Poly1305 — изобретён Daniel J. Bernstein. Не использует таблицы (атаки по таймингу невозможны), работает на чистых сложениях/XOR. На ARM-мобильниках без AES-NI в 3 раза быстрее AES. Поэтому Google пушит ChaCha для мобильного TLS.

WireGuard, Signal, modern OpenSSH — только ChaCha20-Poly1305. TLS 1.3 поддерживает оба, выбор по согласованию.

3. Diffie-Hellman: как договориться о ключе на виду у всех

Главная проблема: Алиса и Боб хотят общий симметричный ключ, но между ними враждебный канал (провайдер всё слышит). Как? Diffie-Hellman (1976) — алгоритм, который позволяет двоим договориться о секрете через открытый канал, и наблюдатель не сможет его вычислить.

Аналогия

Краски

Алиса и Боб публично договариваются о «жёлтой» базовой краске. Каждый дома берёт свою секретную краску (Алиса — синюю, Боб — красную) и смешивает с жёлтой. Алиса получает «зелёный» (жёлтый+синий), Боб — «оранжевый» (жёлтый+красный).

Они обмениваются полученным цветом по открытому каналу. Алиса добавляет свою синюю краску к «оранжевому» Боба — получает «коричневый» (жёлтый+красный+синий). Боб добавляет свою красную к «зелёному» Алисы — получает тот же коричневый.

Подслушивающий видит «жёлтый», «зелёный», «оранжевый». Чтобы получить коричневый — нужно знать «синий» или «красный» отдельно. Но разделить смешанную краску обратно — практически невозможно. Это и есть «one-way function» — основа DH.

В реальной криптографии «краска» — это операция возведения в степень в конечном поле или точка на эллиптической кривой. Восстановить «секретную краску» (дискретный логарифм) — задача, которая на классическом компьютере требует миллиардов лет. На квантовом — час. Поэтому ECDH скоро заменят на ML-KEM (см. урок 14).

X25519 Diffie-Hellman — handshake побайтово
Alice 1. Генерирует: priv_A = random(32B) pub_A = X25519(priv_A, G) 2. Получает pub_B 3. Вычисляет общий: shared = X25519(priv_A, pub_B) 4. shared → HKDF → ключ для ChaCha20-Poly1305 Bob 1. Генерирует: priv_B = random(32B) pub_B = X25519(priv_B, G) 2. Получает pub_A 3. Вычисляет общий: shared = X25519(priv_B, pub_A) 4. То же shared! математика гарантирует Открытый канал (провайдер слышит всё) pub_A: 32 байта pub_B: 32 байта подслушивающий видит pub_A и pub_B но не может вычислить shared
X25519 — ECDH на кривой Curve25519. Используется в WireGuard, TLS 1.3, Signal, SSH

PFS — Perfect Forward Secrecy

Защита

Что такое PFS и почему без неё нельзя

Если Алиса использует один и тот же priv_A годами — у атакующего есть шанс. Он может годами записывать твой трафик, а потом украсть приватный ключ (взлом, повестка, продажа провайдера) — и расшифровать ВСЁ накопленное.

PFS = на каждое соединение (или каждый час) генерируется НОВАЯ пара (priv_A, pub_A) — «ephemeral keys». После handshake ephemeral ключи уничтожаются. Даже если потом украдут постоянный ключ — старый трафик уже не расшифровать, его ключ удалён.

Без PFS: RSA-key-exchange в старом TLS, PPTP, IPsec без DH-PFS-режима. С PFS: TLS 1.3 (всегда), WireGuard, Signal, modern OpenVPN.

4. Noise Framework — как WireGuard и Reality строят handshake

Дизайнерам протоколов надоело каждый раз заново изобретать «как обменяться ключами безопасно». Trevor Perrin (соавтор Signal) формализовал процесс — Noise Protocol Framework (noiseprotocol.org). Это «конструктор» handshake'ов из стандартных блоков.

В Noise описывают последовательность операций сторон в виде паттерна. Самые популярные:

Буквы означают:

Например, Noise_IK — клиент анонимен (отсутствие первой буквы статика для I), но знает сервер заранее (K). Это и есть WireGuard: ты знаешь публичный ключ сервера, сервер не знает твой статический заранее.

Под капотом

Шаги Noise_IK (упрощённо)

  1. Клиент генерирует ephemeral e_i, вычисляет DH(e_i, S_r) — общий промежуточный ключ
  2. Шифрует этим ключом свой статический публичный ключ S_i и шлёт серверу
  3. Сервер расшифровывает S_i, вычисляет DH(S_r_priv, S_i) — второй ключ → миксует
  4. Сервер шлёт свой эфемер e_r, вычисляет DH(e_r, e_i) — третий ключ → миксует
  5. Итоговый chaining key от смешивания 3-4 DH → HKDF → симметричные ключи для ChaCha20

Главное — даже зная статические ключи обеих сторон, нельзя расшифровать прошлый handshake без эфемеров, которые уже стёрты. Это и есть PFS на уровне дизайна.

5. TLS 1.3 handshake — побайтово

Reality, Trojan, Hysteria, MASQUE — все маскируются под TLS 1.3. Без понимания, что внутри ClientHello — не понять, как Reality умудряется не иметь сертификата. Разберём.

TLS 1.3 — 1-RTT handshake
Client Server ① ClientHello внутри: • version: TLS 1.2 (legacy!) • random: 32 байта • cipher_suites: [AES-GCM, ChaCha20-Poly1305] • extensions: - SNI = "example.com" ← DPI ВИДИТ - supported_versions = [TLS 1.3] ② ServerHello + EncryptedExtensions + Certificate + Finished • version: TLS 1.2 (legacy) • random: 32 байта • cipher_suite: AES-GCM • key_share: pub_S (X25519) → дальше уже ЗАШИФРОВАНО ③ [Encrypted] Finished + Application Data
TLS 1.3: один round-trip и канал готов. SNI пока виден всем — это и блокирует ТСПУ
0000 16 03 01 02 00 TLS record: type=Handshake(22), legacy ver 0x0301 0005 01 00 01 fc Handshake: type=ClientHello(1), len=508 0009 03 03 legacy_version = TLS 1.2 (для обратной совместимости) 000B b3 9e 42 c1 ... [32 bytes] client_random (32 байта) 002B 20 [32 bytes] legacy_session_id (тоже 32 байта) 004C 00 08 13 02 13 03 13 01 00 ff cipher_suites: TLS_AES_256_GCM, ChaCha20-Poly1305... 0056 01 00 compression: null 0058 01 8b extensions_length = 395 005A 00 00 00 12 00 10 00 00 0d SNI extension (type 0) 0063 65 78 61 6d 70 6c 65 2e 63 6f 6d "example.com" ← ДПИ читает это в plaintext! 006E 00 0a 00 0e 00 0c ... supported_groups 00C5 00 33 00 24 00 1d 00 20 key_share extension, X25519, 32 байт pub_C
Атака

SNI в ClientHello — главная дыра TLS 1.3

В строке 0x005A-0x006D видно поле SNI = «example.com» в чистом виде. Это и есть то, что читает ТСПУ за наносекунды: «о, человек идёт на example.com — режем».

Это фундаментальная проблема TLS 1.3: чтобы сервер на shared IP отдал нужный сертификат, клиент должен сказать имя до handshake. Решение придумали — ECH (Encrypted Client Hello) — шифрование SNI публичным ключом провайдера хостинга. В 2025-2026 ECH медленно раскатывается. Подробно — в уроке 14.

Reality решает это иначе: подставляет SNI настоящего популярного сайта (apple.com) и реально проводит handshake с этим сайтом, а уже потом «угоняет» канал. ДПИ видит handshake с apple.com — пропускает. Подробно — в уроке 7.

6. MTU/MSS в туннеле — почему важно

Когда твой пакет заворачивается в внешний — оба занимают место в одном Ethernet-кадре. Если MTU кадра 1500 байт, а внешний header VPN съел 60 байт — внутреннему остаётся 1440. Если приложение это не знает и шлёт 1500-байтовые пакеты — будет фрагментация или drop.

ПротоколOverhead (примерно)Рекомендуемый inner MTU
WireGuard (UDP)32 (header) + 16 (auth tag) + 20 (IP) + 8 (UDP) = ~801420
OpenVPN (UDP)~50-701430-1450
OpenVPN (TCP)~70-901410
IPsec ESP (tunnel mode)~60-80 (зависит от шифра)1420
WireGuard через TCP-обёртку~1001400
Shadowsocks-AEAD-2022~30-501450
Под капотом

Path MTU Discovery и почему это ломается

Идеально — клиент послал бы 1500-байтовый пакет, и где-то посредине маршрутизатор сказал бы ICMP «Fragmentation Needed»: «уменьши до 1380». Это PMTUD. Проблема: половина роутеров режет ICMP — пакет молча теряется. Это и есть «чёрная дыра MTU».

Решение: MSS clamping. Маршрутизатор VPN-туннеля переписывает поле MSS в TCP-SYN на безопасное значение (обычно MTU − 40). Тогда TCP сам не отправит больше. В Linux: iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu.

7. Резюме — что унести в следующие уроки

  1. Гибридная схема: асимметрика (Curve25519) для key exchange — раз в handshake; симметрика (ChaCha20-Poly1305 или AES-GCM) для трафика — постоянно.
  2. AEAD обязателен. Любой VPN в 2026 без AEAD — устаревший (PPTP, OpenVPN с BF-CBC). Без аутентификации шифр уязвим к подмене.
  3. PFS обязателен. Ephemeral keys на каждое соединение. Кража долгосрочного ключа не должна расшифровать прошлый трафик.
  4. Noise Framework — фундамент современных VPN (WireGuard). Понимаешь Noise_IK — понимаешь WireGuard.
  5. SNI в TLS 1.3 — главная щель. На этом построены все блокировки и большинство современных контрмер (Reality, ECH).
  6. MTU теряет 30-90 байт на туннеле. Всегда настраивай MSS clamping или явный inner MTU — иначе будут «работает 80% сайтов».

В следующем уроке — конкретные протоколы: OpenVPN, IPsec, L2TP, PPTP, SSTP. Каждый с побайтовой разборкой формата пакета, состояниями и анализом «как DPI его видит». После этого WireGuard (урок 04) ляжет за полчаса.