A bot detector, a fraud team and a threat hunter all want an answer to the same question: what software is actually at the other end of this connection? The obvious place to look is the user agent string, and the obvious problem is that the client writes it. Anyone can claim to be Chrome on a Mac.

The interesting signal is the part the client does not choose deliberately. Long before any HTTP request, the connection opens with a TLS handshake, and its first message is a detailed, unencrypted description of the client's cryptographic capabilities. Nobody types it; the TLS library emits it, the same way every time, and different libraries emit it differently. JA3 and JA4 are two ways of boiling that message down to a short string you can match on.

What gets measured, and when#

A TLS connection begins with the client sending a ClientHello. There is no shared key yet, so it travels in the clear, where the server and anything on the path can read it.

Packed into it are the TLS versions the client will speak, its cipher suites in preference order, a list of extensions, the elliptic curves it knows, the protocols it wants to negotiate (ALPN, the extension carrying h2 for HTTP/2), and the hostname it is reaching for.

None of that is a claim about identity. It is a byproduct of how the client was built: which TLS library, which version, compiled with which options. Chrome, curl, a Python script using requests, a Go binary and malware statically linked against an old OpenSSL all produce visibly different hellos. That difference is the fingerprint.

How JA3 is built#

JA3 was created by John Althouse, Jeff Atkinson and Josh Atkins at Salesforce, and open sourced in 2017. It takes five fields out of the ClientHello:

SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat

Each field holds the decimal values from the packet, joined by hyphens, with commas between fields. The resulting string is hashed with MD5 to give a 32-character fingerprint.

MD5 is doing no security work here. It is a labelling function, turning a variable-length list into a fixed-length token you can index, share in a report and grep for in a log; our post on what hashing actually does covers where the algorithm matters and where it does not.

One design decision is worth holding on to, because it is the one that later broke. JA3 hashes the lists as they were sent, in the order they appeared.

Why the server half matters too#

The same authors published a companion, JA3S, built from the ServerHello using three fields rather than five: SSLVersion,Cipher,SSLExtension. Their reasoning explains why one fingerprint is often not enough. A client using Python, or just the operating system's TLS sockets, shares its JA3 with an enormous amount of ordinary software, so alone that hash is not a detection. But the server's answer is a fingerprint too, and a command-and-control server responds in ways ordinary web servers do not. Pair the two and you have a signature of the negotiation itself, which holds whatever IP address, domain or certificate the operator moves to next week.

What broke JA3#

Two things, one small and one fatal.

The small one is GREASE, specified in RFC 8701. Clients advertise randomly chosen reserved values among their cipher suites and extensions, and a correct server must not negotiate any GREASE value it is offered. The point is to keep the ecosystem honest: servers constantly offered values they do not recognise get their intolerance found and fixed early, rather than blocking the next protocol change years later. For fingerprinting, it means an implementation that fails to strip those values computes a fresh hash every connection. JA3 tooling learned to drop them.

The fatal one arrived in early 2023, when Chrome began permuting the order of the extensions in its ClientHello on stable releases. The motivation was the same ossification worry that produced GREASE: a fixed order invites servers to hard-code an assumption about what Chrome looks like, and every such assumption makes the next TLS change harder to ship. Only pre_shared_key stays pinned, because TLS 1.3 requires it last.

Because JA3 hashes the extension list in the order it was sent, a shuffled order produces a different hash. Chrome's JA3 became a value that changes from one connection to the next, which is precisely what a fingerprint must not do.

It is worth being exact about the damage. JA3 did not stop working; it stopped working for clients that randomise. A malware sample, a scraper on a fixed HTTP library or an appliance with a frozen TLS stack still fingerprints as reliably as in 2017. What you can no longer do is recognise a modern browser.

What JA4 changed#

JA4 is the answer, developed by John Althouse at FoxIO with input from engineers at GreyNoise, Hunt, Google, ExtraHop, F5 and Driftnet. Three changes matter.

  • The lists get sorted before hashing. Ciphers into hex order, extensions likewise. Shuffling what you send no longer changes the value, which neutralises the problem that killed JA3.
  • Part of the fingerprint stays readable. JA3 is 32 opaque hex characters: you either have it in a database or you have nothing. A JA4 opens with a short readable prefix an analyst can decode at a glance.
  • The hash is truncated SHA-256 rather than MD5, 12 lowercase characters per section.

GREASE handling is written into the specification rather than left to the implementer: the program "needs to ignore GREASE values anywhere it sees them", when counting and when hashing.

One more detail is a real gain in accuracy. A TLS 1.3 client pins the legacy version field to 1.2 for compatibility, so that field tells you almost nothing. JA4 instead specifies that "if extension 0x002b exists (supported_versions), then the version is the highest value in the extension", using the legacy field only when the extension is absent, so the version characters say what the client can really speak. See TLS 1.2 vs TLS 1.3 for what separates them.

Reading a JA4 string#

A JA4 has three sections separated by underscores. Take the specification's own example:

t13d1516h2_8daaf6152771_e5627efa2ab1

The first section, t13d1516h2, decodes character by character:

  • t is the transport: t for TLS over TCP, q for QUIC, d for DTLS.
  • 13 is TLS 1.3. Older versions take 12, 11, 10, s3 and s2, and DTLS has its own codes.
  • d means an SNI extension is present, so the client named a domain. i means no SNI, which typically means it connected straight to an IP address.
  • 15 is fifteen cipher suites, with GREASE values not counted.
  • 16 is sixteen extensions, again ignoring GREASE.
  • h2 is the first and last alphanumeric characters of the first ALPN value, here HTTP/2. When there is no ALPN at all the field is 00.

The second section is a 12-character truncated SHA-256 of the cipher list sorted in hex order, GREASE removed. The third gives the extension list the same treatment, with two deliberate omissions: SNI (0x0000) and ALPN (0x0010) are pulled out before hashing, since both already appear in the prefix. The signature algorithms are then appended in their original order, separated by an underscore inside the string that gets hashed.

That omission has a useful consequence. Those two extensions vary with where the client is going rather than with what it is, so leaving them in would give one browser a different fingerprint for every site it visited. Taking them out means the hash describes the client.

The rest of the JA4+ family, and its licence#

JA4 is one member of a suite that also covers the server's TLS response (JA4S), HTTP clients (JA4H), latency (JA4L), X.509 certificates (JA4X), SSH (JA4SSH) and TCP clients (JA4T), with more for DHCP, NTP and active scanning.

Check the licensing before the engineering, because it is split. JA4 itself is BSD 3-Clause with no patent claim by FoxIO. The other methods are patent-pending under the FoxIO License 1.1, which the project describes as permissive for academic and internal business use but not for monetisation; a vendor shipping them in a commercial product needs an OEM licence. That catches people out, since the family is documented in one place and reads at a glance as one open-source project.

What a TLS fingerprint cannot tell you#

A JA4 identifies a software stack, not a person and not a machine. Every copy of the same browser build on the same platform produces the same value. For bot detection that is the point: a client claiming to be Chrome while carrying a Go standard-library fingerprint has contradicted itself. For attribution it is a hard ceiling, since the fingerprint puts you in a bucket of millions.

Nor is a match evidence of intent. A fingerprint tied to a malware family is really a fingerprint of the library and settings that family used, and it matches every benign program built the same way. Treat it as one input, in the spirit of ranking addresses when triaging IPs in a log, not as a verdict. It can also be imitated: make a client emit another client's hello and its JA4 follows. Which is why the useful signal is usually a mismatch between layers, a user agent and a TLS fingerprint that disagree, rather than any single value.

Encryption is closing in on part of this, though less than headlines suggest. Encrypted Client Hello, standardised in March 2026 as RFC 9849, encrypts an inner ClientHello under the server's public key, hiding the SNI and ALPN list. An outer ClientHello still goes out in the clear, so the shape a JA4 measures does not disappear; what ECH removes is the destination detail beside it. Its keys live in DNS, in the type covered by our post on SVCB and HTTPS records.

Where this sits next to browser fingerprinting#

The word "fingerprint" covers two techniques at different layers, and they are easy to conflate. A TLS fingerprint is computed by the server from one connection. It needs no JavaScript, no page load and no cooperation from the client, and works on anything that speaks TLS, a one-line curl included. It identifies the stack.

A browser fingerprint is computed inside the page, by script, from properties the browser exposes: canvas rendering, the GPU reported through WebGL, audio quirks, screen geometry, installed fonts. It tries to identify the individual installation, and is far more distinctive as a result. Our browser fingerprint tool measures that second kind, and shows which anti-fingerprinting protections your browser has active; what is browser fingerprinting is the longer explanation.

To be clear about what we do not do: nothing on this site reports your JA3 or JA4. Those belong to the connection as the server received it, and a script in the page cannot see them. Reading your own needs a packet capture, or a service built to echo the handshake back.

Try it

See the other half of the picture

A TLS fingerprint describes the stack. A browser fingerprint describes the installation, and it is the half you can actually check for yourself. Run the probe to see your canvas, WebGL, audio, font and navigator signals, an estimate of how many bits of identifying information they add up to, and which of your browser's defences are working.

Check your fingerprint →