Snell vs Shadowsocks 2022
A protocol-level comparison of Snell v4/v5 and Shadowsocks 2022 (SIP022) — what each defends against, what each leaks on the wire, and which threat model picks which.
This page is an investigation, not advocacy. OpenSnell implements Snell — that's our bias up front. The findings below try to be honest about where Snell loses, with line-level citations into both codebases so any claim is verifiable.
The two protocols compared:
- Snell v4 / v5 — Surge's proprietary proxy protocol, re-implemented
in OpenSnell from
components/snell/v4.goandcipher.go. v4 and v5 share the same TCP frame format — we use "Snell" to mean either. - Shadowsocks 2022 / SIP022 — the AEAD-2022 protocol family
(specification SIP022),
read from
shadowsocks-rust'scrates/shadowsocks/src/relay/{tcprelay,udprelay}/aead_2022.rs. We do not cover the older SIP004 AEAD scheme.
TL;DR
| Dimension | Winner |
|---|---|
| Cryptographic protocol design | Shadowsocks 2022 |
| Surviving the GFW with no external wrapper | Snell |
| Surviving the GFW with a modern wrapper | Shadowsocks 2022 + Reality / shadow-TLS |
| Recovering plaintext from a capture (no PSK) | Tie — both unbreakable |
| Identifying the protocol from a capture | See § The high-entropy paradox — the question splits in two |
| Multi-user / key rotation | Shadowsocks 2022 (EIH) |
| Implementation maturity / audits | Shadowsocks 2022 |
The verdict is contextual. There is no axis on which one protocol is strictly better than the other.
How to read this page
"Secure" and "uncensorable" use the same word for different things. Each finding below is tagged with which adversary it applies to:
- Passive censor. Logs traffic, applies offline classifiers. The GFW circa 2010.
- Active censor. Probes flagged endpoints, attempts replay, fingerprints handshakes. The GFW since ~2020.
- Network observer / researcher. Captures pcaps and tries to identify what protocol is in use. No interaction with the server.
- Cryptographic adversary (no PSK). The standard threat model. What can they do with ciphertext alone?
- Adversary with PSK. Has somehow obtained the pre-shared key. What can they still not do?
The two protocols make very different tradeoffs across these.
First packet on the wire
A side-by-side of what the very first TCP segment looks like for each:
Snell (first frame)
+-------------+----------------------------+----------------------+----------------------+
| salt (16B) | AEAD(header_7B) + tag(16B) | padding(256..511 B) | AEAD(payload) + tag |
+-------------+----------------------------+----------------------+----------------------+
| plaintext | header_7B = [0x04, 0, 0, | bytes at *even* | first payload is the |
| | padLen_be, payloadLen_be]| indices SWAPPED with | CONNECT request: |
| | | even-indexed payload | [ver, cmd, cid_len, |
| | | ciphertext bytes — | cid, hostlen, host, |
| | | padding never | port_be] |
| | | contiguous on wire | |
+-------------+----------------------------+----------------------+----------------------+The padding bytes are picked by
makeV4Padding
to push the overall ones/zeros ratio of salt + padding + payload
toward 1.6 or 0.4 — explicitly away from the 1.0 ratio that
uniform-random AEAD output produces. The first segment is shaped to
not look like high-entropy noise.
Shadowsocks 2022 (first request)
+-------------+--------------------------------+-----------------------------------+
| salt | AEAD(fixed_header) + tag(16B) | AEAD(variable_header) + tag(16B) |
| (16 or 32B) | fixed_header (11B) = | variable_header = |
| | [type=0, ts_be(8), len_be(2)] | [ATYP, addr, port_be, |
| | | padLen_be(2), padding(0..900), |
| | | payload] |
+-------------+--------------------------------+-----------------------------------+
| plaintext | exactly 27 ciphertext bytes | uniform random bytes throughout |
+-------------+--------------------------------+-----------------------------------+The padding is uniform random. There is no statistical shaping.
From byte 0 onward (after the salt prefix), every byte is
indistinguishable from /dev/urandom output.
Cryptographic primitives
Key derivation
| Property | Snell | Shadowsocks 2022 |
|---|---|---|
| KDF | Argon2id(t=3, m=8KiB, p=1) | BLAKE3::derive_key |
| Inputs | PSK string + 16 B salt | PSK bytes + per-stream salt |
| PSK assumption | Arbitrary string (weak OK) | High-entropy 16 / 32 B (openssl rand -base64 16) |
| Cost per connection | One Argon2 call (~ms, 8 KiB RAM) | One BLAKE3 call (~µs) |
The choice reflects a different threat model assumption:
- Snell assumes the operator might pick a weak PSK like
mypassword123. Argon2id makes that PSK expensive to brute-force from a captured handshake. It costs ~1 ms and 8 KiB of memory per new TCP connection on the server. - Shadowsocks 2022 assumes the PSK is already a uniformly random 16- or 32-byte key. Under that assumption, Argon2 buys nothing — you can't brute-force a 128-bit random key regardless of how slow the KDF is. The spec explicitly forbids the EVP_BytesToKey password expansion that Shadowsocks AEAD-2017 used.
Neither approach is wrong. They're optimized for different operator populations.
AEAD
| Property | Snell | Shadowsocks 2022 |
|---|---|---|
| Cipher | AES-128-GCM only | AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305 (+ optional ChaCha8) |
| Nonce | 12 B counter, init 0, per-AEAD-op increment | 12 B counter, init 0, per-AEAD-op increment |
| Max plaintext / chunk | 0x3FFF (16383) | 0xFFFF (65535) |
Both use the standard counter-nonce pattern, which is safe for AES-GCM provided the key is fresh (which it is — derived from a per-stream random salt).
Algorithm agility is where the gap shows: Shadowsocks 2022 supports ChaCha20 for ARM / embedded systems without AES-NI hardware acceleration, and AES-256 for operators with a stronger key requirement. Snell is hard-coded to AES-128-GCM.
The 4× larger chunk size (65535 vs 16383) cuts framing overhead proportionally for bulk transfers — relevant on long-haul links where goodput matters.
Protocol-level defenses
This is where the gap is widest, and where Shadowsocks 2022 unambiguously wins.
Replay protection
| Defense | Snell | Shadowsocks 2022 |
|---|---|---|
| Per-stream salt | ✅ | ✅ |
| Server salt cache | ❌ | ✅ (60 s) |
| Timestamp window | ❌ | ✅ (30 s, aead_2022.rs:377-379) |
What an attacker can do against Snell, without ever decrypting:
- Capture a complete client-initiated TCP session.
- Replay the exact byte sequence to the same server hours later.
- The server will Argon2-derive a fresh session key from the salt (the salt is in the capture), accept every frame's GCM tag (they verify, since the replayed nonce sequence matches), execute the CONNECT to whatever host was originally requested, and faithfully reproduce the original session — possibly fetching the same URL, re-triggering side effects on the upstream.
This is a real protocol-level weakness in Snell. Shadowsocks 2022 catches the same replay because (a) the salt is in the 60-second cache during the legitimate session and (b) the embedded timestamp will be well outside the ±30 s window on replay.
Direction binding
| Defense | Snell | Shadowsocks 2022 |
|---|---|---|
| Type byte in header (0=client/1=server) | ❌ | ✅ |
Shadowsocks 2022 puts a type byte at the start of every AEAD'd
header chunk: 0 for client→server, 1 for server→client. The receiver
checks it (aead_2022.rs:367-373). An attacker can't take a
server-side ciphertext and feed it back to the server as if it were
client traffic.
Snell has no such field. The first byte of the AEAD'd header is a
fixed 0x04 (version constant), which is identical in both directions.
The two streams are still cryptographically separated because each
direction has its own salt → its own key → its own nonce counter, so a
swap would fail tag verification. But the defense is implicit (from the
key separation) rather than explicit (from a domain separator), which
historically has been the source of cross-protocol attacks.
Request → response binding
Shadowsocks 2022 server responses embed the client's request salt
inside the encrypted response header (aead_2022.rs:382-387). The
client checks it before trusting the response. This binds every
response to its specific request — preventing an active attacker who
controls the network from splicing one server's response to a
different client's request.
Snell has no equivalent.
Multi-user / identity
Shadowsocks 2022 ships Extensible Identity Headers (EIH, SIP022-2). A single server can hold N user keys; each request carries an encrypted hash that identifies which user it's from. This lets you:
- Run one shared server for many users without one user's PSK compromise affecting the others.
- Rotate keys without downtime by adding a new user, migrating clients, then removing the old one.
Snell has a single PSK. One leak → everyone re-keys.
Anti-censorship behavior
This is the axis where Snell catches up — and on the bare-protocol level, surpasses.
What Snell does
- First-frame padding shaping.
makeV4Paddingcomputes a target number of1bits for the padding such that the combinedsalt + padding + payloadbit distribution lands at roughly 61.5 % ones (or 28.5 % if zeros outnumber ones in the AEAD output). The intent is explicit: avoid the ~50/50 ratio that uniform-random AEAD output produces, because "perfectly uniform random bytes" is itself a DPI heuristic for encrypted-proxy traffic. - Padding interleave. Even-indexed padding bytes are swapped
with even-indexed bytes of the payload ciphertext
(
swapPadding). Padding bytes never appear contiguously on the wire, so a passive observer can't count "runs of suspiciously-random bytes" and isolate the padding region. - Dynamic Record Sizing (v5). The first frame on a stream is small (~1.5 KB), with subsequent frames in the same burst growing until they saturate the 16 KB chunk cap. Idle for 30 s resets back to small. This mimics the size distribution of normal HTTPS application records rather than the "constant max-size frames" pattern of bulk proxies.
- Built-in
http/tlsobfs. Static fake-handshake wrappers for TCP. These are 2014-era templates and don't fool a 2025-era DPI on their own, but they layer over the AEAD frames at zero extra cost.
What Shadowsocks 2022 does
At the protocol level: nothing. The first packet after the salt prefix is uniform-random ciphertext. The optional 0–900 B request padding is random bytes, not statistically shaped.
The Shadowsocks 2022 design intentionally delegates anti-censorship to external transports:
shadow-tls— wraps the Shadowsocks stream inside a real TLS 1.3 handshake against a chosen upstream domain.v2ray-plugin— WebSocket / mKCP / QUIC carriers.- REALITY — TLS handshake stealing from a real upstream, with the proxy data ride-along.
With any of these wrappers, Shadowsocks 2022 is at least as hard to
detect as Snell's static tls obfs — usually meaningfully harder,
because the wrappers do real TLS rather than replaying a 2014
ClientHello template.
Net result
| Scenario | Verdict |
|---|---|
| Bare protocol over plain TCP, GFW circa 2025 | Snell (probably) — the entropy shaping was designed for exactly this |
| With a modern wrapper (shadow-TLS, REALITY, etc.) | Shadowsocks 2022 — better wrappers exist in its ecosystem |
With Snell's built-in http / tls obfs only | Marginal improvement over bare Snell — the templates are old enough to fingerprint |
Note the "probably" on the first row. We have no public, current data on whether the GFW actually misses Snell v4/v5 today. Snell's anti-fingerprinting design is from 2018-era thinking; the GFW has had seven years to adapt. The bit-count shaping itself is a fingerprint (see next section).
The high-entropy paradox
Both protocols can be observed in pcap. Without the PSK, neither is decryptable — AES-GCM holds. So the meaningful question becomes "can the observer tell what protocol this is?" — and that question splits into two opposing answers:
Answer 1 (the GFW's perspective). Can a passive classifier flag this as "an encrypted proxy"?
- Shadowsocks 2022 has perfect uniform-random byte distribution after the salt. This is, ironically, easier to flag — it matches the signature of every encrypted proxy in existence. The Wu et al. USENIX Security 2023 paper "How the Great Firewall of China Detects and Blocks Fully Encrypted Traffic" documents the GFW using exactly this heuristic against bare Shadowsocks variants.
- Snell's bit-count shaping deliberately pulls the distribution away from 50/50, into a range that looks more like real TCP/TLS payload. A naive entropy classifier sees it as more "normal" traffic.
Answer 2 (the protocol-identification perspective). Can someone who already knows what to look for tell which specific protocol this is?
- Shadowsocks 2022 has no static field anywhere in the visible bytes. Once a session is past the salt, there's nothing structural that says "this is Shadowsocks". You can only confirm by attempting decryption — which requires the PSK.
- Snell's bit-count shaping is itself a fingerprint. Any first-frame
whose
salt + padding + payloadlands at roughly 61.5 % or 28.5 % ones, with apadLenfield consistent with that interval, is identifiable as Snell. Once a researcher publishes that classifier, passive identification becomes feasible.
These two answers point in opposite directions:
| Asked question | Easier-to-flag | Harder-to-flag |
|---|---|---|
| "Is this an encrypted proxy?" | Shadowsocks 2022 | Snell |
| "Is this Snell vs. Shadowsocks vs. something else?" | Snell | Shadowsocks 2022 |
The GFW cares about question 1 (block-by-class). A reverse-engineer cares about question 2 (identify-by-protocol). The protocols make opposite tradeoffs.
Findings matrix
| Adversary | Snell | Shadowsocks 2022 |
|---|---|---|
| Cryptographic (no PSK) | Plaintext not recoverable | Plaintext not recoverable |
| Cryptographic (with PSK) | Full plaintext + all session data + can replay sessions | Full plaintext, but cannot replay (timestamp + salt cache) |
| Passive censor (entropy classifier) | Lower detection probability (shaped padding) | High detection probability (uniform random) |
| Active censor (replay probe) | Vulnerable — full session replay works | Resistant — salt cache + timestamp window |
| Active censor (handshake fingerprint) | Built-in http/tls obfs is dated and fingerprintable | No built-in obfs; must combine with shadow-TLS / REALITY |
| Researcher (passive identification) | Bit-count shaping is itself a fingerprint | No static field; identification only via decrypt attempt |
| Multi-tenant operator | Single PSK, no per-user keys | EIH — N keys, hot-rotatable |
When to pick which
- Pick Snell if your bias is toward "must just work in the Chinese network today, with minimal extra infrastructure", you're the only user, and you accept the cryptographic protocol's age (no replay protection, no direction binding, single PSK).
- Pick Shadowsocks 2022 if you need multi-tenancy, you want a protocol that's been independently implemented and audited, or you plan to layer a modern transport (shadow-TLS, REALITY, etc.) and don't care that the bare protocol is easy to flag.
- Pick both, on the same host, on different ports, if you have the budget for it. The threats they fail against barely overlap.
Caveats and limits of this analysis
- No live GFW data. This page asserts that Snell's anti-entropy shaping helps "in 2025" without giving you a measurement. We do not have a current, controlled study of which protocols the GFW actually flags. The relative ranking here is based on what each protocol makes available to a classifier, not on observed block rates.
- No active probing analysis. We did not test what either server does in response to an active probing attempt (replayed handshake, malformed first frame, etc.). Both protocols should close the connection silently, but probing-resistance is its own research area.
- Snell is a black-box protocol. It has no public specification. All Snell-related claims here are derived from reverse-engineering documented at MetaCubeX/mihomo#2816 and from this repository's own implementation. Surge could change the wire format and invalidate any of these findings in a future release.
- Shadowsocks 2022 is a moving target too. The SIP022 spec has
been extended (EIH, UDP) since the original 2022 publication. We
reference the current
masterofshadowsocks-rustand the live spec on the Shadowsocks-NET/shadowsocks-specs repo as of this writing. - "Better" is a contextual word. Every "winner" cell in the tables above is conditional on the threat model in its row. There is no overall winner.
References
- Snell v4/v5 wire format —
components/snell/v4.go,cipher.go. See also Protocol reference on this site. - Shadowsocks 2022 spec — SIP022-1: 2022 edition, SIP022-2: Extensible Identity Headers.
- Shadowsocks 2022 reference implementation —
shadowsocks-rust,crates/shadowsocks/src/relay/tcprelay/aead_2022.rsand the parallel UDP file. - GFW detection of fully-encrypted traffic — Mingshi Wu et al., "How the Great Firewall of China Detects and Blocks Fully Encrypted Traffic", USENIX Security 2023.
- Modern Shadowsocks transports — shadow-tls, REALITY, v2ray-plugin.