TLS Fingerprinting (JA3, JA4): Ukryty Identyfikator
You've spoofed your canvas fingerprint, rotated your IP address, and configured your anti-detect browser perfectly. But your TLS handshake just told Cloudflare exactly what software is making the connection — before a single HTTP request was sent.
TLS fingerprinting identifies clients based on the TLS ClientHello message, which is sent unencrypted at the start of every HTTPS connection. It's the fingerprinting vector that most operators don't know exists, and the one that's hardest to fake.
How TLS Handshakes Identify Clients
Every HTTPS connection begins with a TLS handshake. The client sends a ClientHello message to the server containing:
- TLS version — the maximum TLS version the client supports
- Cipher suites — the encryption algorithms the client supports, in preference order
- Extensions — additional TLS features the client supports (SNI, ALPN, key share groups, signature algorithms)
- Supported groups — the elliptic curves the client supports for key exchange
- Signature algorithms — the signature algorithms the client accepts
Each browser, HTTP library, and TLS stack has a unique combination of these parameters. Chrome, Firefox, Safari, curl, Python requests, Go's net/http, and Node.js all produce different ClientHello messages — even when they're connecting to the same server and requesting the same page.
The critical detail: the ClientHello is sent before encryption begins. Any network intermediary (CDN, firewall, ISP) can read it. And because it's a function of the software's TLS implementation rather than user configuration, it's consistent across all connections from that software.
Here's what makes it powerful for detection: if your browser sends a user-agent claiming to be Chrome 120, but the TLS ClientHello matches Python's requests library, the mismatch is definitive proof that the request isn't coming from a real Chrome browser.
JA3: The Original TLS Fingerprint
JA3 was created by John Althouse, Jeff Atkinson, and Josh Atkins at Salesforce in 2017. It's a method for creating a fingerprint hash from the ClientHello message.
How JA3 Hashing Works
JA3 concatenates five fields from the ClientHello, separated by commas:
TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
Example for Chrome 120 on Windows:
771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,
0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-21,
29-23-24,0
This string is then MD5-hashed to produce a compact fingerprint:
JA3: cd08e31494f9531f560d64c695473da9
The JA3 Database
Security researchers maintain databases of known JA3 hashes mapped to software:
| JA3 Hash | Software |
|---|---|
cd08e31494f9531f560d64c695473da9 |
Chrome 120 (Windows) |
b32309a26951912be7dba376398abc3b |
Firefox 121 (Windows) |
e7d705a3286e19ea42f587b344ee6865 |
Safari 17 (macOS) |
795bc7ce3e4dfe5f65b526e8fcc112a5 |
Python requests 2.31 |
56c7fb264de0268b5f7a5a14cd7f2c97 |
curl/8.4.0 |
3b5074b1b5d032e5620f69f9f700ff0e |
Go net/http |
When a server (or CDN like Cloudflare) receives a connection, it computes the JA3 hash and compares it to the user-agent header. A request claiming User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 but carrying JA3 hash 795bc7ce3e4dfe5f65b526e8fcc112a5 is immediately identified as Python requests pretending to be Chrome.
JA3 Limitations
JA3 has several weaknesses that JA4 addresses:
- TLS 1.3 reduced the fingerprint surface. TLS 1.3 standardized many parameters, making JA3 hashes less distinctive between browsers.
- GREASE (Generate Random Extensions And Sustain Extensibility). Chrome and other browsers inject random cipher suite and extension values to prevent ossification. These random values change the JA3 hash across connections from the same browser.
- MD5 collisions. The hash is MD5-based, which allows for intentional collisions and doesn't support partial matching.
JA4: The Evolution
JA4 was created by John Althouse (the JA3 creator) at FoxIO in 2023 to address JA3's limitations. It's part of a broader JA4+ suite that includes JA4S (server fingerprint), JA4H (HTTP client fingerprint), JA4T (TCP fingerprint), and JA4X (X.509 certificate fingerprint).
JA4 Improvements
GREASE handling. JA4 ignores GREASE values (0x0a0a, 0x1a1a, etc.) so the fingerprint is stable across connections that include random GREASE extensions.
Readable format. Instead of an opaque MD5 hash, JA4 uses a structured format:
t13d1516h2_8daaf6152771_e5627efa2ab1
This breaks down as:
t— protocol (t=TCP, q=QUIC)13— TLS version (13=TLS 1.3, 12=TLS 1.2)d— SNI present (d=domain, i=IP)15— number of cipher suites16— number of extensionsh2— ALPN first value (h2=HTTP/2, h1=HTTP/1.1)_8daaf6152771— truncated hash of sorted cipher suites_e5627efa2ab1— truncated hash of sorted extensions + signature algorithms
Sorted values. JA4 sorts cipher suites and extensions before hashing, so the order they appear in the ClientHello doesn't affect the fingerprint. This prevents trivial evasion by reordering parameters.
TLS 1.3 awareness. JA4 handles TLS 1.3's encrypted extensions and reduced parameter surface by incorporating additional signals (ALPN, extension count, signature algorithms) that remain distinctive.
JA4H: HTTP Client Fingerprinting
JA4H extends fingerprinting to the HTTP layer — the order and values of HTTP headers. Every HTTP client sends headers in a characteristic order:
Chrome: Host, Connection, sec-ch-ua, sec-ch-ua-mobile, User-Agent, Accept...
Firefox: Host, User-Agent, Accept, Accept-Language, Accept-Encoding...
Python: Host, User-Agent, Accept-Encoding, Accept, Connection...
Even when you spoof the user-agent value, the header order identifies the client. JA4H hashes the header names and their order to create a client fingerprint at the HTTP layer.
Defense and Evasion
Matching TLS fingerprints is one of the most technically challenging aspects of stealth operations. Here's what works and what doesn't.
Use the Real Browser's TLS Stack
The most reliable defense: don't try to fake TLS fingerprints. Instead, use the actual browser's TLS stack by automating a real browser. Puppeteer and Playwright driving a real Chrome instance produce Chrome's genuine TLS fingerprint because they're using Chrome's actual BoringSSL implementation.
The problem: headless browser detection has its own vectors. You solve the TLS fingerprint problem but introduce automation detection vectors. The tradeoff depends on which detection is more sophisticated at your target.
TLS Library Configuration
For HTTP client-based operations (not browser-based), some libraries allow TLS configuration that mimics browser fingerprints:
curl-impersonate — a modified curl that mimics Chrome's, Firefox's, or Safari's TLS fingerprint by using the same TLS library and configuration as each browser.
tls-client (Go) — a Go HTTP client that spoofs JA3/JA4 fingerprints by configuring the TLS stack to match target browser parameters.
requests + pyOpenSSL — Python can be configured to use specific cipher suites and extensions, though matching the exact order and set of a real browser requires careful configuration.
import tls_client
session = tls_client.Session(
client_identifier="chrome_120",
random_tls_extension_order=True
)
response = session.get("https://target.com")
HTTP/2 Fingerprinting Awareness
Beyond TLS, HTTP/2 introduces its own fingerprinting surface. The HTTP/2 SETTINGS frame sent at connection start contains parameters that vary by client:
| Parameter | Chrome | Firefox | Safari |
|---|---|---|---|
| HEADER_TABLE_SIZE | 65536 | 65536 | 4096 |
| MAX_CONCURRENT_STREAMS | 100 | — | 100 |
| INITIAL_WINDOW_SIZE | 6291456 | 131072 | 2097152 |
| MAX_HEADER_LIST_SIZE | 262144 | 65536 | — |
| ENABLE_PUSH | — | 0 | — |
| Window Update | 15663105 | 12517377 | 10485760 |
If your TLS fingerprint says Chrome but your HTTP/2 SETTINGS say Firefox, the combination is impossible for a real browser and flags the connection immediately. Tools like curl-impersonate handle both TLS and HTTP/2 fingerprints together.
FAQ
Does Cloudflare actually use JA3/JA4 for bot detection? Yes. Cloudflare's Bot Management product explicitly uses TLS fingerprinting as one of its detection signals. Their documentation confirms this. Akamai, Imperva, and DataDome also use TLS fingerprinting. It's a standard component of commercial bot detection.
Can I just randomize my TLS parameters? No. Random TLS parameters don't match any known browser and are immediately flagged as anomalous. The goal is to match a specific real browser's fingerprint exactly, not to create a unique one. Uniqueness in TLS fingerprints is suspicious — real browsers have known, catalogued fingerprints.
Does a VPN change my TLS fingerprint? No. A VPN encrypts your traffic in a tunnel, but within that tunnel, your browser's TLS ClientHello is unchanged. The target server sees the same TLS fingerprint regardless of whether you're using a VPN, proxy, or direct connection. VPNs change your IP; they don't change your TLS fingerprint.
How does QUIC/HTTP/3 affect TLS fingerprinting? QUIC integrates TLS 1.3 into the transport layer, changing the fingerprint surface. JA4 accounts for this with the protocol indicator (q=QUIC vs t=TCP). QUIC fingerprinting is newer and less studied, which means detection is less mature — but also that evasion techniques are less developed.
If I use Puppeteer with real Chrome, is my TLS fingerprint safe? Yes for the TLS layer — Puppeteer drives real Chrome, which produces Chrome's genuine TLS fingerprint. But Puppeteer introduces other detectable artifacts (CDP protocol, navigator.webdriver, missing plugins) that may identify the connection as automated even though the TLS fingerprint is correct. See automation detection avoidance.