Skip to content
Blog

TLS fingerprinting explained: JA3 vs JA4

A JA3 or JA4 fingerprint summarizes a TLS ClientHello. How each is built, how to read and capture one, and why a TLS fingerprint alone never proves a bot.

Frederick Jahn
Published Updated
TLS fingerprinting explained: JA3 vs JA4

A TLS fingerprint is a compact description of a client's TLS handshake. It can help distinguish a standard browser connection from a script or reveal a mismatch between a claimed browser and the network client making the request. It does not identify a person, prove that a request is automated, or explain what the client intends to do.

JA3 and JA4 are two ways to turn fields from a TLS ClientHello into a value that can be compared across connections. JA3 preserves the order of several lists. JA4 sorts cipher and extension IDs before hashing them, so extension permutation does not produce a new hash for those parts.

Start with the ClientHello

The ClientHello is the client's opening TLS handshake message. In TLS 1.3 it contains a legacy version field, a random value, a session ID, offered cipher suites, compression methods, and extensions. The extensions can describe supported TLS versions, groups, signature algorithms, key shares, the server name, and application protocols such as HTTP/2. The exact structure is defined in RFC 8446.

An abbreviated observation might look like this:

transport: TLS over TCP
highest supported version: TLS 1.3
cipher suites: 1301, 1302, 1303, ...
extensions: server_name, supported_groups, alpn, key_share, ...
first ALPN value: h2

Those fields reflect choices made by the client's TLS stack and its configuration. Different programs may produce different combinations. The same program can also change its handshake after an update, on another operating system, or when configured differently.

The collection point matters. The original ClientHello is available where the visitor's TLS connection terminates. If a CDN terminates that connection and opens a new one to your origin, the origin sees the CDN's handshake, not the visitor's. Collect the fingerprint at the trusted TLS boundary and carry it forward as connection metadata if the application needs it.

From handshake to fingerprint

A practical pipeline has six steps:

  1. Capture the ClientHello at the TLS termination point.
  2. Parse only the bounded fields required by the chosen fingerprint format.
  3. Remove or normalize values exactly as that format specifies.
  4. Calculate the fingerprint and record the format and implementation version.
  5. Attach it to the connection or request without treating it as authenticated identity.
  6. Compare it with other evidence available for the same request and session.

Do not invent a private variation and still call it JA3 or JA4. A lookup is only portable when producers use the same field selection, GREASE handling, sorting, and hashing rules.

JA3 and JA4 compared

PropertyJA3JA4
Primary inputTLS version, offered ciphers, extensions, supported groups, and EC point formatsTransport and protocol metadata, counts, ALPN, ciphers, extensions, and signature algorithms
List orderPreserves cipher, extension, group, and point-format orderSorts cipher IDs and extension IDs; signature algorithms remain in their presented order
GREASEExcludes GREASE valuesExcludes GREASE values
OutputMD5 of the comma-and-hyphen-delimited inputA readable prefix plus two 12-character truncated SHA-256 hashes
Useful propertySimple, widely recognized representation of the observed list orderMore stable when a client permutes cipher or extension order
Main limitationHarmless ordering changes can create a different valueSorting removes ordering information; shared or imitated handshakes still look alike

Salesforce's JA3 specification defines the five input fields and the ordered string that is hashed. Its implementation also ignores GREASE values so reserved placeholders do not become new fingerprints. Salesforce archived the repository on May 1, 2025, and no longer maintains it. The specification still describes how existing JA3 values were produced.

Reading a JA4 string

FoxIO's JA4 specification builds a value in three parts, a_b_c, such as t13d1516h2_8daaf6152771_b186095e22b6:

PartValueMeaning
a, character 1tTLS over TCP. q means QUIC and d means DTLS
a, characters 2 and 313TLS version, here TLS 1.3
a, character 4dSNI extension present. i means no SNI
a, characters 5 and 615Cipher suites offered, GREASE excluded
a, characters 7 and 816Extensions offered, GREASE excluded
a, last two charactersh2First and last characters of the first ALPN value. 00 means no ALPN
b8daaf6152771First 12 characters of a SHA-256 hash of the sorted cipher list
cb186095e22b6First 12 characters of a SHA-256 hash of the sorted extensions, without SNI and ALPN, followed by the signature algorithms in their original order

Because the parts are separate, you can compare one without the others. FoxIO's README gives the example of tracking a client that rotates ciphers by matching JA4_ac, which keeps parts a and c and drops b. JA4 itself is BSD 3-Clause, like JA3, while most other JA4+ methods use the FoxIO License.

Modern Chromium can permute TLS extensions in the ClientHello to reduce protocol ossification. Chromium's own feature definition describes the permutation behavior. Chromium source An order-sensitive JA3 can therefore change across connections even when the underlying client has not. JA4's sorting makes its cipher and extension hashes stable under that particular change. It does not make JA4 permanent or unspoofable. Some scraping browsers, such as Ulixee Hero, document emulation of browser network and TLS characteristics for this reason.

What GREASE means here

GREASE values are reserved placeholders that clients can send at TLS extension points. They are intended to expose implementations that fail when they see an unknown value, keeping those extension points usable. They are not invalid bytes and their presence is not proof of Chrome or any other browser. RFC 8701 defines the mechanism and the reserved values.

Both JA3 and JA4 exclude GREASE values as part of their normalization. Keep the raw, access-controlled observation separately if GREASE behavior matters to your analysis; it will not be represented fully in the normalized hash.

Reproduce a fingerprint locally

Wireshark exposes both tls.handshake.ja3 and tls.handshake.ja4. JA4 is available in Wireshark 4.2 and later, according to its TLS display-filter reference.

Use this small lab only on a device and traffic you are allowed to inspect:

  1. Record tshark --version, curl --version, the browser version, operating system, destination host, and test time.
  2. Start a Wireshark capture on the interface carrying your traffic.
  3. Make three new TLS-over-TCP connections to the same test host with system curl. Then make three with a normal browser. Avoid a host for which the browser already has a reusable connection.
  4. Stop the capture and save it as tls-lab.pcapng.
  5. Extract the fingerprint fields:
tshark -r tls-lab.pcapng \
-Y "tls.handshake.type == 1" \
-T fields \
-e tcp.stream \
-e tls.handshake.ja3 \
-e tls.handshake.ja4 \
-e tls.handshake.ja3_full

Put the six rows in a table with the client, version, run number, JA3, and JA4. Compare repeated runs before comparing the two clients. You may see changing JA3 values with a stable JA4 when extension order is permuted. You may instead see stable values for both. The result depends on the exact client, build, configuration, and route.

This is a protocol demonstration, not a detection benchmark. Six local connections cannot establish false-positive rates, population frequency, or whether a fingerprint belongs to a bot. HTTP/3 uses QUIC rather than a TCP stream, so keep the first lab to TLS over TCP or adapt the capture and JA4 transport interpretation deliberately.

Use lookup data as context, not identity

A fingerprint database can tell you that a value has previously appeared with a program, browser family, malware sample, or internal client. The record needs provenance: who observed it, with which implementation, on what date, and under which configuration.

There are two different kinds of ambiguity:

  • Many-to-one: different users or programs can produce the same normalized fingerprint. A common browser fingerprint should be expected across many unrelated people.
  • One-to-many: one program can produce different fingerprints after an update, across platforms, or under different TLS settings.

Hash collisions are also possible in principle. JA3 uses MD5 and JA4 truncates SHA-256 outputs, but these values are labels, not cryptographic proof. In operations, shared inputs and changing inputs are usually the more immediate source of mistaken attribution.

Age every lookup entry. Keep an unknown state. A fingerprint that is absent from the database is not automatically suspicious, and an old label should not silently become a permanent block rule.

What a TLS fingerprint cannot prove

A matching fingerprint cannot prove that the request came from the named browser. Tools can deliberately imitate browser handshakes. curl-impersonate changes curl's TLS and HTTP/2 behavior to resemble supported browsers. uTLS exposes low-level ClientHello control and browser-like presets. Both projects also document limits: configuration changes can alter a signature, and ClientHello parroting does not reproduce every later behavior of a browser.

A TLS fingerprint also cannot answer:

  • whether a human or automation controls the client;
  • whether a request is benign, abusive, or authorized;
  • whether two connections belong to the same actor;
  • whether a claimed crawler is operated by the company named in its user agent;
  • whether the client will behave like the imitated browser after the handshake.

For a named crawler, use the operator's published verification method rather than substituting a TLS label. How to verify AI agents covers those methods.

Combine TLS with independent evidence

TLS fingerprinting is most useful as a consistency check. It can raise or lower confidence when compared with evidence from other layers.

ObservationUseful comparisonCaution
Browser user agent with an unfamiliar TLS fingerprintHTTP protocol behavior, browser APIs, navigation sequenceA browser update or unusual platform may be legitimate
Familiar browser JA4 across many sessionsSession behavior, request graph, cookies, challenge resultCommon browsers naturally share fingerprints
Claimed named crawlerPublished IP or DNS verification, documented token, resource policyUser-agent and TLS labels are not operator identity
TLS fingerprint changes mid-sessionConnection reuse, proxy path, network change, session continuityMobile networks and multi-edge routes can change connections
Browser-like TLS plus browser-like HTTPRuntime and session evidenceImpersonation tools can reproduce more than the handshake

The decision model should preserve which layer contributed each signal. A single mismatch may justify observation or a step-up check. Blocking needs a policy that accounts for the route, confidence, impact on legitimate readers, and rollback.

The next layer is covered in How to detect browser automation.

Operational checklist

  • Collect at the boundary that sees the visitor's original ClientHello.
  • Version the parser and fingerprint format.
  • Bound parsing work and retain an explicit unknown state.
  • Store source, date, client version, and configuration with lookup labels.
  • Re-test known fingerprints after browser, TLS library, CDN, or proxy changes.
  • Keep observation separate from enforcement.
  • Measure false positives on your own traffic before blocking.
  • Log the rule version and reason so a decision can be audited and reversed.

Centinel helps teams combine connection, request, browser, and session evidence when classifying automated traffic. Evaluate it with traffic from your own site and verify which signals are available at your TLS termination point. To establish a baseline before enforcing a rule, request a site audit.

Sources