ostp/docs/ru/obfuscation.md

7.4 KiB
Raw Blame History

Обфускация трафика OSTP

Философия

Классические туннельные протоколы (TLS, OpenVPN, WireGuard) имеют узнаваемые сигнатуры в хэндшейке или статичные заголовки пакетов. Механизм обфускации OSTP спроектирован так, чтобы начиная с первого байта трафик был максимально похож на случайный шум — и для DPI-систем был неотличим от него.


Деривация секретов

Все секреты протокола — ключ обфускации, PSK Noise-хэндшейка, диапазон паддинга хэндшейка и маркер junk-пакетов (см. ниже) — выводятся из общего access_key одним проходом HKDF-SHA256, с разделением по доменам через последний байт info:

PRK              = HKDF-Extract(salt = SHA-256(access_key)[0..16], IKM = access_key || PROTOCOL_VERSION)
obfuscation_key  = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x01, 8 байт)
psk              = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x02, 32 байта)
handshake_pad    = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x03, 2 байта)
junk_marker      = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x04, 4 байта)

Версия протокола подмешивается в IKM, а не передаётся открытым байтом на проводе: пиры с разной версией протокола выведут разный obfuscation_key и просто не смогут деобфусцировать пакеты друг друга — жёсткий version gate без единого узнаваемого маркера на проводе. Ни один секрет никогда не передаётся — обе стороны независимо выводят одинаковые значения из общего access_key.


Алгоритм динамического маскирования

Датаграммы OSTP маскируются "на месте" прямо перед отправкой и сразу после получения. Сама маска выводится из шифротекста самого пакета, а не из статичного потока ключа или счётчика — поэтому она меняется от пакета к пакету автоматически:

mask = HMAC-SHA256(key = obfuscation_key, message = ciphertext[0..min(32, len)])

1. Фаза хэндшейка (is_handshake = true)

Пакет на проводе — [4 байта session_id][2 байта noise_len][Noise-полезная нагрузка]. Маска считается по Noise-полезной нагрузке (raw[6..]), и её первые 6 байт накладываются XOR'ом на session_id || noise_len.

2. Фаза передачи данных (is_handshake = false)

Пакет на проводе — [4 байта session_id][8 байт nonce][AEAD-шифротекст]. Маска считается по шифротексту, и её первые 12 байт накладываются XOR'ом на session_id || nonce.

Эффект схемы

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


Статистический паддинг

Помимо маскирования заголовков, OSTP защищается от анализа длин пакетов (Traffic Length Analysis). AdaptivePadder вычисляет случайный размер мусорных байт, добавляемых к полезной нагрузке ещё до шифрования:

  • Динамическое распределение: длины паддинга подобраны так, чтобы напоминать профили длин обычного HTTPS-трафика или видеопотоков.
  • Внутри шифротекста: добавленный паддинг находится внутри области AEAD-шифрования — пассивный наблюдатель не может отличить паддинг от полезной нагрузки и не видит настоящую границу сообщения.

Junk-пакеты и TCP-фрагментация

OSTP не пытается притворяться известным протоколом (TLS, HTTP и т.п.) — фильтр по сигнатуре всегда можно обновить под конкретную имитацию. Вместо этого используется подход в духе zapret: никакого узнаваемого заголовка вообще, плюс активная манипуляция границами пакетов — фингерпринтить попросту нечего.

  • Junk-пакеты: перед хэндшейком клиент отправляет настраиваемое количество (junk_pc) мусорных датаграмм случайного размера (junk_ps). Каждая несёт 4-байтовый маркер, выведенный из access_key (тот самый junk_marker выше), а не фиксированную константу — константный маркер сам по себе стал бы универсальной сигнатурой для любого наблюдателя сразу по всем серверам OSTP. Сервер, перебирая кандидатов-ключей, выводит тот же маркер и тихо отбрасывает junk, не доходя до логирования «unauthorized probe».
  • TCP-фрагментация (только для транспорта UoT/TCP): первый пакет (хэндшейк) режется на мелкие куски (frag_chunk байт) с небольшими задержками (frag_sleep мс) между записями — DPI, анализирующий только первый TCP-сегмент, никогда не видит цельный хэндшейк для фингерпринтинга.

Обе фичи настраиваются per-профиль; ни одна не применяется поверх обычного UDP-транспорта, где отдельная junk-датаграмма выглядела бы для сервера точь-в-точь как случайный одиночный проб.