Portfolio Blog Code Repository Contact

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:

  1. TLS version (e.g., 0x0303 for TLS 1.2)
  2. Cipher suites the ordered list of supported ciphers
  3. Extensions the ordered list of TLS extension types
  4. Elliptic curves (called "supported groups" in TLS 1.3)
  5. 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_certificate extension with Brotli compression
  • application_layer_protocol_negotiation with h2 and http/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_credentials extension (Firefox-specific)
  • ffdhe2048 and ffdhe3072 in 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:

  1. XTLS-REALITY Perfect TLS 1.3, active probe resistant
  2. uTLS Browser-grade JA3 fingerprint, JA3-based DPI resistant
  3. VMess Binary protocol, no fixed signatures
  4. Trojan TLS Application Data encapsulation
  5. 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

  1. Althouse, J., Atkinson, J., and Atkins, J. "JA3 A Method for Profiling SSL/TLS Clients." Salesforce Engineering Blog, 2017. salesforce.com
  2. IETF. "GREASE for TLS." RFC 8701, 2020. datatracker.ietf.org
  3. Rescorla, E. "The Transport Layer Security (TLS) Protocol Version 1.3." RFC 8446, IETF, 2018.
  4. Frolov, S. and Wustrow, E. "The Use of TLS in Censorship Circumvention." NDSS, 2019.
  5. GFW Report. "How the GFW Detects and Blocks Fully Encrypted Traffic." gfw.report (2023)
  6. refraction-networking. "uTLS Low-level access to TLS handshake." github.com/refraction-networking/utls
  7. Bernstein, D. J. "Curve25519: New Diffie-Hellman Speed Records." PKC 2006, LNCS 3958.
  8. XTLS Community. "Xray-core, XTLS & REALITY." github.com/XTLS/Xray-core

Amine Boutouil

Security Architect · Technical Polymath

boutouil.me →