Bots & traffic

7 min readUpdated

What is TLS fingerprinting (JA3 and JA4)?

Quick answer

TLS fingerprinting identifies what software is making a connection by looking at how it negotiates encryption, rather than at what it claims to be. Every client sends a Client Hello listing the cipher suites, extensions and curves it supports, in a particular order, and that combination differs between Chrome, Firefox, curl and a Python script. Hashing it produces a fingerprint (JA3, or the newer JA4) that reveals the underlying library even when the user-agent header says something else entirely.

01How a TLS fingerprint is built

Before any encrypted data flows, the client sends a Client Hello announcing what it supports: TLS versions, cipher suites in preference order, extensions, elliptic curves and point formats. Nothing in it is secret and nothing in it identifies a person. It describes the software, and it is the opening move of the TLS handshake that every HTTPS connection begins with.

JA3, the method that popularised the idea, reads five of those fields as decimal values, keeps them in the order the client sent them, joins them with commas and hyphens, and hashes the string with MD5 into a 32-character fingerprint. Values reserved for GREASE are skipped, so browsers that use GREASE still map to a single hash. Two clients built on the same library and version produce the same hash; a different library produces a different one.

The important property is timing. A server learns something about the client before it has seen a single HTTP header, including the user-agent the client might be lying in.

What goes into a JA3 fingerprint (illustrative values)
TLSVersion,Ciphers,Extensions,EllipticCurves,CurveFormats

771,4865-4866-4867-49195-49199,0-23-65281-10-11,29-23-24,0
         │                        │                │
         │                        │                └─ curves
         │                        └─ extension list, in the order sent
         └─ cipher suites, in the client's preference order

→ MD5 → e195ee6c10d79ea3730f8bf6fca7bcf1

02Why JA4 replaced JA3

JA3 hashes extensions in the order the client sends them, and that order stopped being stable. In November 2022 the Chromium team announced that Chrome would randomise the order of its Client Hello extensions, explaining that a fixed order encourages servers to fingerprint Chrome and then assume specific behaviour. Once that rolled out, the same browser produced a different JA3 hash on almost every connection.

That is a different mechanism from GREASE, which is often blamed for the same effect. GREASE, standardised in RFC 8701, has clients advertise reserved dummy values so that servers stay tolerant of values they do not recognise. JA3 and JA4 both ignore those values.

JA4, published by FoxIO, sorts cipher suites and extensions before hashing, leaves the SNI and ALPN extensions out of the extension hash, and puts a readable summary in front of two truncated SHA-256 hashes. The protocol, TLS version and counts are visible without a lookup table, which makes it far easier to reason about and to debug. The original JA3 repository was archived in May 2025.

JA3JA4
HashOne MD5 of the whole stringReadable prefix plus two truncated SHA-256 hashes
Extension orderIncluded, so randomisation breaks itSorted, so randomisation does not matter
GREASE valuesIgnoredIgnored
TransportTLS over TCPTLS, QUIC and DTLS, marked in the prefix
FamilyJA3S for the server replyJA4S, JA4H for HTTP, JA4L and others

03Reading a JA4 fingerprint

The JA4 specification uses the fingerprint below as its worked example. The first part is plain text; the other two are hashes that only match exactly. Signature algorithms are added to the third hash in their original order, while ciphers and extensions are sorted.

PartValueMeaning
ProtocoltTLS over TCP (q would mean QUIC, d DTLS)
Version13Highest supported TLS version, here 1.3
SNIdThe client sent a domain name (i would mean an IP address)
Cipher count15Cipher suites offered, GREASE excluded
Extension count16Extensions offered, GREASE excluded
ALPNh2First and last character of the first ALPN value, 00 if none
Cipher hash8daaf6152771Truncated SHA-256 of the sorted cipher list
Extension hashe5627efa2ab1Truncated SHA-256 of the sorted extensions plus signature algorithms
The example from the JA4 specification
t13d1516h2_8daaf6152771_e5627efa2ab1
└───a────┘ └─────b────┘ └─────c────┘

a = readable prefix   b = cipher hash   c = extension hash

04What it actually catches

The classic case is a script claiming to be a browser. A Python request sending a Chrome user-agent negotiates like Python, because it is Python, and its fingerprint matches no browser build. The contradiction between claimed identity and observed handshake is the signal.

It also groups activity. If one fingerprint is shared by thousands of addresses that all appeared in the last minute and all request the same login endpoint, that is probably one tool being run at scale, which is the usual shape of credential stuffing and of bulk web scraping.

The idea extends past TLS. Akamai researchers showed in 2017 that HTTP/2 clients can be passively fingerprinted from differences in how they implement the protocol, and JA4H applies the approach to the HTTP request itself. A client whose HTTP behaviour contradicts its TLS handshake is telling two different stories.

ObservationWhat it suggestsWhat it can justify on its own
Browser user-agent, handshake of a scripting libraryA script impersonating a browserStrong evidence, still best combined with other signals
Browser user-agent, matching browser handshakeA real browser, or a headless oneNothing: this is what most visitors look like
Library handshake with an honest user-agent such as curlAutomation that is not hidingYour normal policy for API and automated clients
A handshake no current browser shipsOld software, embedded devices or toolingCloser review, not an automatic block, because old devices exist
One fingerprint, many new addresses, one endpointOne tool run from many networksInvestigation, together with behaviour and request rate

05The limits, and the trap

A fingerprint is shared, not unique. Every Chrome user on the same version and platform produces the same value, which is good for privacy and a hard constraint for detection: a fingerprint identifies a class of software, never an individual.

That leads to a common and costly mistake. It is tempting to treat many requests sharing one fingerprint as evidence of a botnet, but legitimate browser traffic is extremely homogeneous, because most visitors run one of a handful of current browser builds. Real traffic can easily be more uniform than the attack you are trying to catch, which is why sound bot management treats a fingerprint as context rather than a verdict.

06Can it be evaded?

Yes, and increasingly easily. Libraries such as uTLS exist specifically to parrot the Client Hello of popular browsers, and a headless browser needs no impersonation at all because it genuinely is a browser.

This does not make fingerprinting useless. It raises the cost of an attack, which is the actual goal, and a forged handshake still has to agree with everything else the client sends. It does mean the fingerprint must be one input among many. Sound systems combine it with the other ways websites tell bots from humans, such as header coherence, behaviour, network reputation and the outcome of a proof-of-work challenge, and require several to agree before acting.

07How to look at a fingerprint yourself

The quickest way to understand the method is to look at real handshakes. Capture a few Client Hellos with tcpdump and open the file in Wireshark: version 3.6 and later show a JA3 field for each Client Hello, and version 4.2 and later add JA4.

The filter below matches TLS handshake records (content type 22) whose first message is a Client Hello (type 1) on port 443. It assumes the record starts at the beginning of the TCP payload, which holds for the first data segment of a connection, and it only matches IPv4, because tcpdump cannot index into a TCP header carried over IPv6.

It helps to place this in the OSI model: the handshake rides on TCP, and everything it reveals is known before HTTP begins. HTTP/3 is different, because QUIC carries its Client Hello inside protected Initial packets over UDP, so this TCP filter will not see it, although Wireshark can still decode those packets.

Capture Client Hellos for inspection (filter checked with tcpdump -d)
# TLS handshake record (0x16) carrying a Client Hello (0x01) on port 443, IPv4 only
tcpdump -ni eth0 -c 20 -w hello.pcap \
  'tcp dst port 443
   and tcp[((tcp[12:1] & 0xf0) >> 2)] = 0x16
   and tcp[((tcp[12:1] & 0xf0) >> 2) + 5] = 0x01'

# open hello.pcap in Wireshark and filter on: tls.handshake.type == 1

Questions

Frequently asked questions

No. It identifies software, and the same value is shared by everyone running that client version. It is closer to recognising a car model than a number plate.

It is far less identifying than cookies or canvas fingerprinting, because it carries no per-user entropy. It says what software connected, not who is using it.

Only for tooling that is unambiguous and that you never want. For anything else, use it as one input alongside other signals. Blocking a whole browser build because one attacker used it is an easy way to lose real traffic.

The HTTP-layer member of the JA4 family. It fingerprints the request itself, such as the method, version, header names and their order, which catches clients whose HTTP behaviour contradicts their TLS handshake.

Yes. The first character of a JA4 fingerprint records the transport, with q for QUIC, so HTTP/3 clients can be fingerprinted with the same method once their Initial packets are decoded.

For clients that keep a fixed extension order, such as many scripts and older libraries, JA3 still groups them reliably. For current Chrome-based browsers it is unreliable because the extension order changes per connection, so JA4 is the better default.

Sources

Sources and verification

Capture filter checked with tcpdump 4.99.3 (tcpdump -d), the JA3 example hash recomputed with md5sum and the JA4 example hashes recomputed with sha256sum, on Debian 12, October 2026.

  1. 01JA4 technical details · FoxIO
  2. 02JA3 (salesforce/ja3, archived repository) · Salesforce
  3. 03Intent to Ship: TLS ClientHello extension permutation · Chromium blink-dev
  4. 04RFC 8701: Applying GREASE to TLS extensibility · IETF
  5. 05Display filter reference: Transport Layer Security (tls.handshake.ja3, tls.handshake.ja4) · Wireshark
  6. 06uTLS (refraction-networking/utls) · Refraction Networking
  7. 07Passive Fingerprinting of HTTP/2 Clients (white paper) · Akamai / Black Hat Europe 2017

Last updated