What exactly is FakeTLS doing to my packets?
FakeTLS takes an existing proxy protocol (like MTProto or Shadowsocks) and rewrites the initial bytes so that a passive observer — a DPI box sitting on the wire — sees a standard TLS 1.3 ClientHello instead of random-looking proxy chatter. It works by wrapping the first chunk of your real payload inside a valid ClientHello record structure. The proxy server then responds with a forged ServerHello, and both sides switch to a custom obfuscated framing that mimics TLS records (type 23, application data) until the connection dies.
The trick is not encryption — you already have that. The trick is plausibility. A DPI system that just checks "is there a ClientHello at the start" will let you through. Here is what a raw TLS record header looks like: 16 03 01 for the handshake, followed by a two-byte length. The ClientHello itself must contain a version (03 03 for TLS 1.2, 03 04 for TLS 1.3), a random 32-byte value, a session ID, cipher suites, and extensions. FakeTLS fills these fields with real-looking data, and the actual proxy handshake bytes are spliced into the session ID or an extension field that servers and clients normally ignore.
The weakness: a modern DPI stack does not stop at the record header. It parses the full handshake, checks that the Session ID length matches the actual bytes, and verifies that the supported_versions extension lists TLS 1.3. A sloppy implementation using 03 03 as the version and a 16-byte session ID looks like TLS 1.2 at best, or garbage at worst. You cannot fake correctness without implementing the state machine.
Why does the SNI domain choice matter so much?
The SNI (Server Name Indication) field is a plaintext hostname inside the ClientHello. Your DPI box reads it. Your ISP reads it. The GFW reads it. Choosing microsoft.com because you heard it was "safe" is a mistake. You are not just picking a name — you are picking the traffic profile associated with that name.
Consider two scenarios. You connect to a FakeTLS proxy with SNI apple.com. The handshake completes, the connection stays alive for 45 minutes, and you transfer 2GB of data. Real Apple clients hitting Apple servers connect, request a few resources, and close within seconds. Your connection pattern is statistically anomalous. Choose update.microsoft.com and you get a slightly better story because Windows Update traffic is long-lived and bursty — but now your HTTP User-Agent or Telegram timestamps don't match.
Also, DPI systems keep blocklists of SNI domains. If your FakeTLS implementation uses a hardcoded list of 20 domains (e.g., cloudflare.com, google.com, bing.com), and the blocklist adds those exact names, your proxy is dead within a week. The practical advice is to randomize from a large pool of high-traffic, globally reachable domains. The tooling in the free-mtproto-proxies repository (https://github.com/dubblebyte/free-mtproto-proxies) does this for Telegram MTProto proxies, but the principle applies to any ForkTLS implementation.
The honest limitation here: SNI alone is not a fingerprint. But the combination of SNI, the exact TLS 1.3 cipher list you offer, and the timing of your packets is a fingerprint. Microsoft's server does not receive a ClientHello that advertises only TLS_AES_128_GCM_SHA256 from a client that then sends no HTTP requests. That gap is what kills the disguise.
How is the ServerHello forged without a real certificate?
In a genuine TLS 1.3 handshake, after the ServerHello the server sends an encrypted extension block containing its certificate and a signature. FakeTLS does not have a certificate. So it does something different: it sends the ServerHello with a fictional key share, then immediately the proxy client and server agree on a shared obfuscation key derived from the ClientHello's random value and the ServerHello's random value.
That means the FakeTLS ServerHello omits the Certificate and CertificateVerify messages entirely. To a passive observer, this looks like a TLS handshake that was interrupted or a session being resumed via a pre-shared key (PSK). TLS 1.3 supports PSK modes natively, and in those modes the certificate exchange is optional. A half-open connection that ends with just ClientHello and ServerHello is also common when a client is probing connectivity.
This is where the fake breaks. A real TLS 1.3 ClientHello that wants to use a PSK must include the pre_shared_key extension. FakeTLS implementations often skip it. A DPI parser that checks for extension presence will see a ClientHello with supported_versions and keyshare, but no pre_shared_key. That's a contradiction. The fix used by stronger implementations is to literally append a dummy pre_shared_key extension with a random binder value. It works, but it adds ~50 bytes of overhead per connection, and it still only buys you time against a generic filter — not against a system that replays the handshake to a real server.
What fingerprinting weaknesses still get it flagged?
The big one is the Random field. In a real ClientHello, the first byte is a timestamp and the remaining 31 bytes are randomness. FakeTLS tools often fill the Random field with the proxy's internal session ID or a repeated byte pattern. A handshake that starts with 03 04 and has a timestamp of 2025 is not suspicious. A handshake whose Random field is all zeros is immediately suspicious.
Here is a table of the known weak points I have seen in the wild:
| Fingerprint Point | What real TLS looks like | What FakeTLS does (often) |
|---|---|---|
| ClientHello version |
03 04 (TLS 1.3) consistently |
03 01 or 03 03 (TLS 1.2) |
| Extensions order | Grease, then supported_versions, then keyshare | Random order, missing Grease |
| Session ID length | 0 or 32 bytes | Arbitrary length (e.g. 16 or 64) |
| Record header type |
16 only for handshake |
Sometimes uses 17 or 18 for SPDY |
| Client TLS cadence | Retransmission backoff of 1s, 3s, 7s | Delays may vary wildly |
| SNI case | Lowercase, no trailing dot | PascalCase or trailing dot |
Another tell is the TLS Application Data records after the handshake. In real TLS 1.3, the first application data record is encrypted and has no plaintext length predictability. FakeTLS often sends a fixed-size MTProto packet (for Telegram, that's a packet with a 12-byte header followed by the encrypted payload). A DPI system that tracks record lengths over a window of 100 records and finds that every record is exactly 2049 bytes will flag you. Mitigation is adding random padding to each record, up to ±512 bytes. That adds bandwidth overhead.
A subtle but fatal issue: GC and TLS session tickets. A real TLS server sends a NewSessionTicket after the handshake. FakeTLS proxies do not, because they have no TLS state to track. A persistent observer that sees zero TLS tickets over 100 connections starts to form a strong profile that you are not a real browser.
Can you tell the difference at the wire level just by watching packets?
Yes, with decent accuracy, if you can see the full handshake and are willing to do statistical analysis. The single strongest signal is the latency to first response. A genuine TLS client connecting to a CDN, say cloudflare.com, rounds trip is around 20–40 ms. A FakeTLS proxy running on a VPS in a neighboring country adds 80–150 ms to that. The DPI system does not need to crack the encryption — it just correlates your handshake timing against the SNI domain's actual geographical location. If your SNI is google.com but the source IP of the proxy is a residential IP in Tehran and the handshake takes 180 ms, the false positive rate is high.
The second wire-level signal is the bit pattern of the keyshare data. In TLS 1.3, the keyshare contains a 32-byte scalar for X25519. The highest bits of that scalar are masked to a fixed value per the RFC. A generator that does not implement the Montgomery curve clamping will produce a scalar with the top bit set — which a DPI system that checks the byte 0x40 mask will catch. This is not a theoretical concern; automated scanners for this specific bit pattern are shipped by at least one major security vendor.
If you want to test your own FakeTLS proxy, use openssl s_client against your proxy's IP. If it says read R BLOCK immediately after the handshake, your proxy is leaking that there is no TLS server behind the port. A real TLS endpoint would send an application-level response or a close_notify alert, not a hard RST. The correct fix is to have the FakeTLS proxy forward a dummy 200 OK HTTP response after the handshake completes, then terminate. That adds ~140 bytes and creates a fuller illusion.
Why do some MTProto proxies survive for years and others die in days?
Survival is not about the obfuscation strength; it's about the target audience. Free public MTProto proxy lists get scraped by automation immediately after being posted. The scripts that publish them, like the one that maintains the live listing in the free-mtproto-proxies repository, are tracked. A proxy that appears on a public list has a life expectancy measured in days, not months. The ones that survive are the ones that are not on a public list — they were shared privately or were barely advertised.
The second survival factor is behavioral. A proxy that rotates its FakeTLS handshake every 10 connections is harder to fingerprint than one that uses a fixed template. Forcing a proxy to rotate its Random field, the SNI domain, and the exact cipher suite order every few hours is a low-cost win. It also avoids the "static fingerprint" problem where an adversary uses a machine-learning classifier trained on your exact byte sequence.
The admission that few implementers make: you are not defeating a targeted adversary. If the GFW knows your proxy's IP and port, they will block you regardless of how perfect your TLS handshake is. FakeTLS defeats automated classification and mass filtering. It does not defeat a blocked IP list. The recent experience of Telegram proxy lists in places like Iran illustrates this — proxies were not defeated by DPI changes, they were defeated by the infrastructure being moved or the backend IPs being blocked wholesale.
So the correct mental model is: FakeTLS shifts you from "immediately blocked by pattern matching" to "requiring an analyst to actively investigate," which gives you an operational lifespan of weeks instead of hours.
How do I pick a good SNI domain for my own setup?
You want a domain that is (1) globally available, (2) has high traffic volume, and (3) is not on a blocklist. You also want the domain to match the behavior you are faking. If your proxy runs on port 443, pick a domain whose real service uses port 443 exclusively. pool.sks-keyservers.net was a classic choice for this but is now defunct. Good modern candidates include connectivity-check.ubuntu.com (low traffic but not monitored), www.msftconnecttest.com (Microsoft's captive portal test, nearly always allowed), and 203.0.113.1 which is not a domain but an IP doc range.
Avoid domains with a huge geographic skew. www.baidu.com looks fine in Asia but is suspicious if your ClientHello comes from a VPS in Frankfurt. Similarly www.baidu.com on the GFW is a red flag if your connection is long-lived. The safest approach is to generate a list of the top 100 websites by traffic (available from public audit lists) and then, at runtime, pick one randomly and persist the choice for at least five minutes to keep the handshake patterns stable. The free-mtproto-proxies project has a configuration option to do exactly this for Telegram.
Also consider the server certificate mismatch. A DPI box can perform a TLS handshake on its own to the real domain, fetch the real certificate, and compare the bytes in your ClientHello's keyshare or random to what a real client would have sent. You cannot pass that test — your ClientHello was generated in Python, not by Chrome. The only mitigation is to use a domain where the actual server does not version-pin its TLS stack to a single configuration, and to keep your cipher list close to what a modern browser offers.
One more pragmatic rule: set SO_RCVTIMEO to 10 seconds on the proxy socket. A real TLS server would have timed out any half-open connection after 30 seconds, but a proxy that hangs around for 5 minutes on a dead handshake is a signature. That specific SO_RCVTIMEO value rarely matters in a fingerprint model, but it prevents resource exhaustion on your side and makes your server behave more like a real overloaded CDN.
FAQ
Is FakeTLS detectable by the GFW in 2025?
Yes, on a deep packet inspection level, but not in real time for most cases. Automated filtering at an ISP scale rarely parses the full TLS 1.3 handshake; they cache SNI and IP blocks. The moment you use a fixed SNI and a fixed handshake template, a machine-learning classifier with 50 milliseconds of CPU time per packet can flag you with 95% accuracy.
Does FakeTLS help with Telegram specifically?
Yes. Telegram's MTProto proxy protocol already has an obfuscated mode, but that mode produces random bytes that are trivially identified by entropy analysis. FakeTLS replaces those random bytes with a structurally valid TLS handshake. It hides the fact that you are using Telegram from a passive observer, which is useful in countries where Telegram itself is blocked, not just the proxy IP.
How much overhead does FakeTLS add to each connection?
Typically 115–140 bytes of extra overhead per handshake, accounting for the fake padding and the pre_shared_key extension. On a long-lived connection of 1 MB of transfer, that is negligible (0.01%). However, if your application opens a new connection per message (Telegram does this for private chats), the overhead can amount to 5–10% of total bandwidth. To mitigate this, you must implement connection reuse aggressively.
Top comments (0)