- Network
- A
ShadowTLS: hiding the tunnel behind a TLS handshake
How the protocol borrows someone else's handshake, why v1 and v2 were flawed, what exactly Aparecium found in v3, and where this approach hits an architectural ceiling
Most proxy protocols try to look like HTTPS: they generate their own certificate, set up TLS, and hope DPI doesn’t look too closely. ShadowTLS takes a different path — it takes someone else’s handshake entirely, without forging anything
While the TLS handshake is happening, all the traffic analyzer sees is an honest ClientHello, an honest ServerHello, a genuine certificate from the real donor server. Everything that happens after the handshake completes is tunneled traffic. By that time, DPI should have already decided to allow the connection.
Before reading further. As of March 2026, ShadowTLS v3 does not work everywhere and not always. In June 2025, the Aparecium tool revealed two reproducible detection vectors: a fixed difference in the length of ServerFinished and incorrect handling of NewSessionTicket from OpenSSL servers. Against providers with basic SNI-blocking without active probing, the protocol works fine. Against DPI with active probing, the risk is real. The article describes the architecture and mechanism, and at the end analyzes where and why detection works.
Three nodes and how they work
Between the client and the proxy server, there is an additional component: ShadowTLS itself.
When connecting, ShadowTLS proxies the TLS handshake to the real donor server. The client receives a real certificate, signed by a real CA — this is why active probing by a DPI system does not work: the prober also gets a real response from a real server.
After the handshake is completed, the connection switches to the proxy. This switch happens inside the already established TCP stream, which from the network’s perspective looks like a continuation of the TLS session with the bank.
How authentication changed: v1, v2, v3
V1
Had no protection against active probing at all. The protocol simply proxied the TLS handshake to the donor, then switched the stream to shadowsocks. The author explicitly described this assumption: v1 assumed that the man-in-the-middle only looked at the handshake and did not perform active probes. The assumption did not survive the first encounter with reality.
V2
V2 added client authentication via challenge-response. The challenge is the entire traffic from the donor server during the handshake (not just ServerRandom: this is easier to implement and less dependent on TLS parsing details).
The client computes an HMAC-SHA1 over this data using a pre-shared key and sends the first 8 bytes of the result in the header of the first Application Data packet.
The probe without the key cannot guess the correct 8 bytes — the connection goes to the donor. The problem of data integrity after the handshake remained unresolved.
V3
V3 redesigned two layers: client authentication before the handshake and data integrity after it.
SessionID as an authentication channel. In TLS 1.3, the SessionID field in ClientHello is not used as intended — browsers fill it with pseudo-random bytes. ShadowTLS v3 places the client’s signature there:
SessionID[0:28] = random
SessionID[28:32] = HMAC(password, ClientHello)[0:4]
The server checks the signature upon receiving ClientHello. If the check fails, the TCP stream goes to the donor unchanged.
HMAC chain for data. Each frame after the handshake carries a 4-byte HMAC signature. The state is updated after each frame — you cannot insert or reuse a frame without knowing the password. This closes the attack where a man-in-the-middle takes data from one direction and sends it back in the other — the signatures will not match. After computing the HMAC, the 4-byte tag itself is additionally fed to the instance — this protects against merging two adjacent ApplicationData frames into one.
Aparecium: what exactly was found in June 2025
On May 31, 2025, the tool Aparecium appeared with the description: “ShadowTLS v3 hereby declared dead due to its inherent design flaws” 1
This is based on two specific findings.
ServerFinished is 4 bytes longer than normal. ShadowTLS v3 adds HMAC tainting to handshake frames, including ServerFinished. The standard TLS 1.3 ServerFinished from an OpenSSL server is 53 or 69 bytes long. Through ShadowTLS v3 it comes as 57 or 73 bytes long
OpenSSL standard: ServerFinished = 53 bytes
Through ShadowTLS: ServerFinished = 57 bytes
Aparecium first makes a direct request to the donor server and records the lengths of its NewSessionTicket messages. Then it connects via ShadowTLS and compares the lengths — if ServerFinished is 4 bytes longer, the protocol is identified.
Incorrect handling of NewSessionTicket. In TLS 1.3, OpenSSL servers send one or two NewSessionTicket messages after ClientFinished. ShadowTLS v3 stops relaying messages from the donor after ClientFinished — and these messages do not reach the client. Aparecium detects their absence by comparing it with what is received when connecting directly to the donor.
Both detection methods work independently of each other. The first provides a deterministic result, while the second provides a probabilistic result, but with high accuracy.
Exception. Go-based servers (Google and its services) send NewSessionTicket before ClientFinished, not after. ShadowTLS manages to relay it — the first detection vector is inactive. However, the second (ServerFinished length) remains.
Configuration
To understand the architecture and use it where advanced DPI is absent:
wget https://github.com/ihciah/shadow-tls/releases/latest/download/shadow-tls-x86\\_64-unknown-linux-musl
chmod +x shadow-tls-x86_64-unknown-linux-musl
mv shadow-tls-x86_64-unknown-linux-musl /usr/local/bin/shadow-tlsServer:
shadow-tls \
--v3 \
server \
--listen 0.0.0.0:443 \
--server 127.0.0.1:8388 \
--tls addons.mozilla.org:443 \
--password your-strong-passwordClient:
shadow-tls \
--v3 \
client \
--listen 127.0.0.1:1080 \
--server your.server.ip:443 \
--sni addons.mozilla.org \
--password your-strong-passwordCheck donor for NewSessionTicket:
echo | openssl s_client -connect your-donor.com:443 -tls1_3 2>&1 | grep -i "session ticket"If the output is empty — the donor does not send NST, and the first detection vector is inactive. The second (4 bytes) remains in any case.
Donors without NewSessionTicket:
*.google.com
*.googleapis.com
*.gstatic.comComparison with Reality
The protocols have different points of effort application.
ShadowTLS proxies the handshake through a real donor — the client receives a real certificate from a real CA and checks it in the standard way.
Reality works differently: the server checks the shortId from the SessionID field in ClientHello. If the shortId matches the config, the server generates a certificate on the fly, signed with the x25519 key from the config. The client verifies it using the pinned publicKey, not via the Apple or Microsoft chain. If the shortId is unrecognized, the server transparently proxies the TCP stream to the donor, the probe gets a real response, and leaves.
Parameter | ShadowTLS v3 | VLESS + Reality |
|---|---|---|
Donor certificate | Real, CA chain | Generated on the fly, pinned key |
Requires Xray | No | Yes |
Requires domain | No | No |
ServerFinished anomaly | +4 bytes, detectable | No |
NST handling | Vulnerable (OpenSSL donors) | Partially vulnerable |
Configuration complexity | Low | Medium |
Relevance against DPI | Questionable | Higher |
Where ShadowTLS makes sense
Against advanced DPI with active probing — risky. Aparecium is a proof-of-concept, not an industrial system, but the detection vector is described accurately and reproducibly.
Against ISP-level SNI blocking without active probing — works. For quick setup without Xray and complex configs — a reasonable choice.
Architectural ceiling
HMAC tainting adds 4 bytes to ServerFinished. They cannot be removed without breaking the authentication mechanism. Forging NewSessionTicket messages from OpenSSL donors after ClientFinished — technically feasible, the Aparecium authors themselves described how: the server should send generated NSTs of the required length instead of missing ones.
The first limitation is architectural, the second is implementation-related. A patch for the second is possible, a patch for the first would require a complete redesign of the authentication scheme.
As of early 2026, there is no updated version with fixes, and the last commit was 11 months ago.
1. ban6cat6/aparecium: Aparecium is a proof-of-concept tool designed to detect TLS camouflage protocols
2. ihciah/shadow-tls: A proxy to expose real tls handshake to the firewall
3. ShadowTLS: A Better TLS Camouflage Proxy | Next Stop - Ihcblog!
Write comment