uTLS: Stealing Chrome's Identity to Defeat JA3 Fingerprinting
The Last Fingerprint
We've spent this entire blog series making BPP traffic look like legitimate protocols. The entropy matches. The packet sizes match. The timing patterns match. Even the SNI field contains domains that belong on the host OS. By every metric we've covered, BPP traffic blends in perfectly.
But there's one fingerprint we haven't addressed. And it's the one that modern DPI systems are increasingly relying on.
JA3.
What JA3 Is and Why It's a Problem
JA3 was introduced by Salesforce in 2017 as a method to fingerprint TLS client implementations. The idea is elegant and, from an evasion perspective, terrifying.
When a TLS client sends its ClientHello message, it advertises a set of capabilities: which cipher suites it supports, which TLS extensions it uses, which elliptic curves it prefers. Different TLS implementations advertise different capabilities, in different orders.
JA3 takes five specific fields from the ClientHello:
- TLS version (e.g.,
0x0303for TLS 1.2) - Cipher suites the ordered list of supported ciphers
- Extensions the ordered list of TLS extension types
- Elliptic curves (called "supported groups" in TLS 1.3)
- EC point formats
It concatenates these lists, hashes with MD5, and produces a 32-character hex string the JA3 fingerprint. Every TLS implementation produces a different one. Chrome's is different from Firefox's. And critically Go's standard library produces a fingerprint that matches no real browser at all.
A DPI system that maintains a database of known JA3 hashes can immediately flag connections from non-browser TLS libraries. The Great Firewall has been observed using JA3-based classification since at least 2021.
Why REALITY Alone Isn't Enough
Our XTLS-REALITY transport does a brilliant job with most of the ClientHello. The SNI is legitimate. The cipher suites are standard TLS 1.3. The handshake completes correctly. But the ClientHello is still generated by Go's TLS library, which has its own distinctive fingerprint.
A DPI system that compares the JA3 hash against its database would see: "This ClientHello claims to connect to google-analytics.com, but the JA3 hash belongs to a Go application, not a browser. Suspicious."
We needed a way to generate ClientHello messages that fingerprint as actual browsers.
Enter uTLS
Instead of using Go's built-in TLS library for the ClientHello, we construct the message manually, byte by byte, reproducing the exact fingerprint of a target browser.
BPP's uTLS transport supports four browser profiles, randomly selected per session:
Chrome 120
- GREASE values in cipher suites and extensions random sentinel values that Chrome inserts to test server tolerance (RFC 8701)
- TLS 1.3 cipher suites listed first, before TLS 1.2 suites
- The
compress_certificateextension with Brotli compression application_layer_protocol_negotiationwithh2andhttp/1.1
Firefox 121
- No GREASE Firefox doesn't use GREASE values at all (this alone distinguishes it from Chrome)
- ChaCha20-Poly1305 listed before AES-256-GCM (Chrome does the reverse)
- The
delegated_credentialsextension (Firefox-specific) ffdhe2048andffdhe3072in the supported groups
Safari 17
- ECDSA cipher suites ranked before RSA suites
- Support for
secp521r1(a curve Chrome and Firefox typically omit) - GREASE values present (like Chrome), but in different positions
- Distinct extension ordering
Edge 120
- Chromium-based, so similar to Chrome's but not identical
- Subtle differences in extension ordering create a distinguishable hash
The Details That Matter
GREASE Values
GREASE (Generate Random Extensions And Sustain Extensibility) values are random-looking hex values from a
specific set (0x0A0A, 0x1A1A, 0x2A2A, etc.) that Chrome and Safari
inject into their ClientHello. A ClientHello that claims to be Chrome but has no GREASE values is
immediately suspicious. BPP generates fresh GREASE values for each session from the standard set.
Extension Ordering
TLS allows extensions in any order. But real browsers have a consistent order that depends on their
implementation. Chrome always puts server_name (SNI) before
extended_master_secret. Firefox puts supported_versions in a specific position.
Getting the order wrong changes the JA3 hash.
The Discriminator
The BPP server needs to tell a uTLS client from a real browser. BPP embeds a discriminator the ASCII bytes
UTLS in a specific position of the ClientRandom field. The ClientRandom is 32
bytes of random data in every TLS ClientHello. DPI systems don't analyze its contents. The BPP server checks
those specific bytes. If they spell UTLS, it's a BPP session. If not, the server falls back to
REALITY behavior.
uTLS vs. REALITY: Complementary, Not Competing
| Threat | REALITY | uTLS |
|---|---|---|
| Protocol signature analysis | ✓ Perfect TLS 1.3 | ✓ Perfect TLS 1.3 |
| Active probing | ✓ Proxies to real site | ✗ No cover site |
| JA3 fingerprinting | ◔ Go's default fingerprint | ✓ Matches real browser |
| SNI-based blocking | ✓ Legitimate domains | ✓ Legitimate domains |
REALITY defeats active probing but has a non-browser JA3 hash. uTLS has a perfect browser fingerprint but doesn't have a cover website for probe resistance. Together, they cover different bases in the threat model.
The Bigger Picture
With uTLS integrated, BPP's Chameleon Engine now has five transport strategies:
- XTLS-REALITY Perfect TLS 1.3, active probe resistant
- uTLS Browser-grade JA3 fingerprint, JA3-based DPI resistant
- VMess Binary protocol, no fixed signatures
- Trojan TLS Application Data encapsulation
- Shadowsocks Zero structure, last-resort fallback
Together, they cover the full spectrum of DPI techniques we know about. Signature matching, entropy analysis, active probing, JA3 fingerprinting, behavioral analysis BPP has a defense for each.
Is it the end of the road? Not even close. DPI systems are evolving toward machine learning classifiers, JA4 fingerprinting, and encrypted client hello (ECH) analysis. The arms race continues. But BPP's modular architecture means new disguises can be added without rewriting the core.
The phoenix doesn't need to be immortal. It just needs to keep rising from the ashes.
Sources
- Althouse, J., Atkinson, J., and Atkins, J. "JA3 A Method for Profiling SSL/TLS Clients." Salesforce Engineering Blog, 2017. salesforce.com
- IETF. "GREASE for TLS." RFC 8701, 2020. datatracker.ietf.org
- Rescorla, E. "The Transport Layer Security (TLS) Protocol Version 1.3." RFC 8446, IETF, 2018.
- Frolov, S. and Wustrow, E. "The Use of TLS in Censorship Circumvention." NDSS, 2019.
- GFW Report. "How the GFW Detects and Blocks Fully Encrypted Traffic." gfw.report (2023)
- refraction-networking. "uTLS Low-level access to TLS handshake." github.com/refraction-networking/utls
- Bernstein, D. J. "Curve25519: New Diffie-Hellman Speed Records." PKC 2006, LNCS 3958.
- XTLS Community. "Xray-core, XTLS & REALITY." github.com/XTLS/Xray-core