Protocol Design
Overview
OSTP (Obfuscated Secure Transport Protocol) is a UDP-based encrypted tunnel designed for maximum resistance to Deep Packet Inspection (DPI) and Traffic Shaping (TSPU) systems.
Design Principles
- Kerckhoffs's Principle — Security depends solely on the access key. The binary contains no protocol-specific strings or magic constants.
- Per-packet indistinguishability — Every byte on the wire is cryptographically random. No handshake signatures, version fields, or fixed patterns.
- Key-derived everything — Obfuscation masks, PSK, and even padding ranges are derived from the access key via HKDF-SHA256.
Wire Format
Handshake Packet
[session_id:4][noise_len:2][noise_payload:N][random_padding:var]
↑ XOR-masked ↑ Cleartext (used to derive mask)
- session_id (4 bytes) — Random per-session identifier, XOR-masked
- noise_len (2 bytes) — Length of Noise payload, XOR-masked
- noise_payload (N bytes) — Noise_NNpsk0 handshake message
- random_padding (key-derived range) — Random bytes to prevent size fingerprinting
Data Packet (Post-Handshake)
[session_id:4][nonce:8][AEAD_ciphertext:N]
↑ XOR-masked ↑ XOR-masked ↑ ChaCha20-Poly1305 encrypted
Obfuscation Mask Derivation
The 6-byte header mask for handshake packets is derived from the Noise payload:
sample = packet[6..38] // First 32 bytes of payload
mask = HMAC-SHA256(obfuscation_key, sample)[0..6]
header ^= mask
For data packets, the mask is derived from the ciphertext:
sample = packet[12..44] // First 32 bytes of ciphertext
mask = HMAC-SHA256(obfuscation_key, sample)[0..12]
header ^= mask
Since the Noise ephemeral key is cryptographically random and the AEAD ciphertext is indistinguishable from random, the mask is unique per packet.
Key Derivation
All secrets are derived from a single access_key using HKDF-SHA256 (RFC 5869):
key_hash = SHA-256(access_key)
salt = key_hash[0..16]
info = key_hash[16..32]
prk = HMAC-SHA256(salt, access_key)
obfuscation_key = HKDF-Expand(prk, info || 0x01, 8)
psk = HKDF-Expand(prk, info || 0x02, 32)
pad_params = HKDF-Expand(prk, info || 0x03, 2)
The padding range is computed from pad_params:
pad_min = 16 + (pad_params[0] % 64)→ range [16, 79]pad_max = pad_min + 48 + (pad_params[1] % 128)→ pad_min + [48, 175]
Different access keys produce different padding distributions, making universal size-based filters impossible.
Noise Protocol
OSTP uses the Noise_NNpsk0 pattern:
Noise_NNpsk0(psk):
→ psk, e
← e, ee
- NN — No static keys (anonymous)
- psk0 — Pre-shared key injected at position 0
- Primitives: X25519, ChaChaPoly, BLAKE2s
The PSK is derived from the access key, preventing unauthorized parties from completing the handshake.
ARQ (Automatic Repeat Request)
OSTP includes a selective-acknowledgment ARQ layer over UDP:
| Parameter | Default | Description |
|---|---|---|
max_reorder |
16384 | Maximum gap between expected and received nonce |
reorder_buf |
8192 | Maximum buffered out-of-order frames |
rto |
100ms | Retransmission timeout |
max_retries |
8 | Maximum retransmission attempts |
DPI Resistance Analysis
What a DPI system sees
- No magic bytes — First bytes are XOR-masked with key-derived randomness
- No fixed packet sizes — Padding range varies per access key
- No protocol strings in binary — HKDF salt/info derived from key hash
- No version fields — Entire header is masked
- UDP packets — Indistinguishable from QUIC, WireGuard, or random UDP traffic
What remains identifiable
- UDP traffic on a fixed port — Mitigated by using common ports (443, 8443)
- Traffic patterns — Mitigated by adaptive padding strategy
- Noise pattern string in binary —
"Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s"is standard and shared by thousands of projects