Coronium Mobile Proxies
Questions on bots and proxies

JA3 and JA4 Compared

JA3 and JA4 both turn the first message of a TLS connection into a short identifier for the software that sent it. JA3 hashes the fields in the order they arrive; JA4 sorts them first and keeps a readable prefix. That one design change is why JA4 survived Chrome's 2023 extension shuffling and JA3 did not. Below: how each is computed, a script that reproduces both, and what a proxy changes.

Coronium Technical TeamSources checked 9 min read

The difference in four lines

  • JA3 is an MD5 of five Client Hello fields in their original order. Salesforce archived the reference repository on May 1, 2025.
  • JA4 is a readable prefix plus two truncated SHA-256 hashes of sorted cipher and extension lists. Reordering extensions does not change it.
  • Chrome 110 began randomizing extension order on every connection, which gave each Chrome build many JA3 values and one JA4 value.
  • An HTTP CONNECT or SOCKS5 proxy passes your TLS bytes through unchanged. Your JA4 belongs to your client; the proxy changes the IP and the TCP layer, not the handshake.

The Client Hello

Every TLS connection starts with the client sending a Client Hello. In TLS 1.3, defined in RFC 8446, that message lists the protocol versions the client supports, the cipher suites it can use, and a set of extensions: server name, supported groups, signature algorithms, key shares, ALPN (which application protocols, such as h2, it speaks) and more. The message travels before encryption starts, so any server or network device on the path can read it.

The contents are chosen by the TLS library, not by the application. Chrome uses BoringSSL, Firefox uses NSS, Safari uses Apple's stack, and Python, Go, Java and curl each bring their own. Every library picks its own defaults, so the Client Hello says a great deal about the software that built it, even when the HTTP headers that follow claim to be something else.

Browsers also insert GREASE values, random-looking placeholders defined in RFC 8701, to keep servers tolerant of unknown values. Both JA3 and JA4 ignore GREASE so that a browser does not get a new fingerprint on every connection for that reason alone.

How JA3 is computed

JA3 was published by Salesforce engineers in 2017. The reference repository describes the method: take five fields from the Client Hello as decimal numbers (the TLS version, the cipher suites, the extensions, the elliptic curves and the elliptic curve point formats), join values inside a field with hyphens and fields with commas, and take the MD5 of that string. The result is a 32-character hex value. A companion method, JA3S, does the same for the server's reply.

JA3's strength was simplicity: one string, easy to log, easy to share in threat intelligence. Its weakness is in the second word of "original order". Because the fields keep the order the client sent them in, any change of order produces an unrelated hash. The repository was archived and marked unmaintained on May 1, 2025, and it points readers to JA4 for current work.

How JA4 is computed

JA4 comes from FoxIO and is specified in its technical details. A JA4 value has three parts separated by underscores, for example t13d1516h2_8daaf6152771_e5627efa2ab1.

The prefix is readable on its own. A defender can write a rule for "TLS 1.3, no SNI, no ALPN" without decoding anything, which is useful because many scripts connect without ALPN while every modern browser offers h2.

JA4 is one member of a family FoxIO calls JA4+, which includes HTTP (JA4H), TCP (JA4T), certificate (JA4X), SSH and latency fingerprints. According to the JA4+ repository, JA4 itself is BSD 3-Clause licensed like JA3, while the other methods use the FoxIO License 1.1, which permits internal and academic use but requires an OEM license to sell fingerprinting as part of a product.

Reproduce both in Python

The script below takes the worked example from FoxIO's specification, computes JA4 and a JA3 value, then shuffles the extension order the way a browser with extension permutation does. It needs only the standard library.

python
"""Compute JA3 and JA4 from Client Hello fields and show what extension shuffling does to each.

The input lists are the worked example in FoxIO's JA4 technical details (hex values,
GREASE already removed). JA3 uses decimal values and keeps the original order.
"""
import hashlib
import random

CIPHERS = "1301,1302,1303,c02b,c02f,c02c,c030,cca9,cca8,c013,c014,009c,009d,002f,0035".split(",")
EXTENSIONS = "001b,0000,0033,0010,4469,0017,002d,000d,0005,0023,0012,002b,ff01,000b,000a,0015".split(",")
SIG_ALGS = "0403,0804,0401,0503,0805,0501,0806,0601".split(",")
CURVES, POINT_FORMATS = ["001d", "0017", "0018"], ["00"]  # example values for JA3 only


def ja4(ciphers, extensions, sig_algs, version="13", sni=True, alpn="h2", transport="t"):
    a = f"{transport}{version}{'d' if sni else 'i'}{min(len(ciphers), 99):02d}{min(len(extensions), 99):02d}{alpn[0]}{alpn[-1]}"
    b = hashlib.sha256(",".join(sorted(ciphers)).encode()).hexdigest()[:12]
    ext = sorted(e for e in extensions if e not in ("0000", "0010"))
    c = hashlib.sha256((",".join(ext) + "_" + ",".join(sig_algs)).encode()).hexdigest()[:12]
    return f"{a}_{b}_{c}"


def ja3(version, ciphers, extensions, curves, point_formats):
    dec = lambda values: "-".join(str(int(v, 16)) for v in values)
    text = ",".join([str(int(version, 16)), dec(ciphers), dec(extensions), dec(curves), dec(point_formats)])
    return hashlib.md5(text.encode()).hexdigest()


print("JA4 ", ja4(CIPHERS, EXTENSIONS, SIG_ALGS))
print("JA3 ", ja3("0303", CIPHERS, EXTENSIONS, CURVES, POINT_FORMATS))

shuffled = EXTENSIONS[:]
random.Random(7).shuffle(shuffled)  # what a browser with extension permutation does per connection
print("after shuffling the extension order:")
print("JA4 ", ja4(CIPHERS, shuffled, SIG_ALGS))
print("JA3 ", ja3("0303", CIPHERS, shuffled, CURVES, POINT_FORMATS))

Output on Python 3.12, October 30, 2026:

text
JA4  t13d1516h2_8daaf6152771_e5627efa2ab1
JA3  c000e2caf3a25423f9de6c8a4b12a975
after shuffling the extension order:
JA4  t13d1516h2_8daaf6152771_e5627efa2ab1
JA3  7a79767bec9f269165daa763726af1cd

The JA4 line matches the value in FoxIO's specification exactly. After the shuffle, JA4 is unchanged and JA3 is a different hash. The simplified function skips edge cases the full specification handles (non-alphanumeric ALPN values, empty lists, raw and original-order variants), so use the reference implementations for production logging.

Reading a JA4 fingerprint
PartExampleMeaning
Transporttt for TLS over TCP, d for DTLS, and a separate letter for the newer datagram transport browsers use for HTTP over that protocol
TLS version13Highest version offered, taken from the supported_versions extension when present
SNIdd if a server name is present, i if the client connected to a bare IP
Cipher count15Number of cipher suites, GREASE excluded
Extension count16Number of extensions, GREASE excluded
ALPNh2First and last character of the first ALPN value, 00 if none
Cipher hash8daaf6152771First 12 hex characters of SHA-256 over the sorted cipher list
Extension hashe5627efa2ab1First 12 hex characters of SHA-256 over the sorted extensions (without SNI and ALPN) plus the signature algorithms in their original order

Why the switch happened

In early 2023, Chrome turned on what its Platform Status entry calls TLS ClientHello extension permutation, shipped in Chrome 110 on desktop, Android and WebView. The stated goal is "to reduce potential ecosystem brittleness": servers and middleboxes had started depending on a fixed extension order, which makes the protocol hard to evolve. The implementation in BoringSSL shuffles the extension list per connection, keeping the pre-shared key extension last as TLS requires.

For JA3, that meant one Chrome build now produced a very large number of different hashes. Rules written against a JA3 list stopped matching real Chrome and started missing things. JA4's sorted lists were unaffected, which is the main reason vendors moved. Fastly announced JA4 support in its bot management product in February 2025, and Cloudflare exposes both JA3 and JA4 to Enterprise Bot Management customers, together with JA4 signals that describe how a fingerprint behaves across Cloudflare's network over the last hour.

That last point matters more than the hash itself. A fingerprint is rarely blocked on sight, because thousands of real users share each common browser value. What gets scored is the combination: a Chrome JA4 with a user agent that says Firefox, a library JA4 with browser headers, or a fingerprint whose traffic pattern across many sites looks like automation.

Check your own fingerprint

Open the JA4 fingerprint checker in the browser or tool you want to test. It shows the JA4 value your client sent and how the prefix decodes. Useful comparisons:

  1. Your everyday browser against the automation browser on the same machine. Playwright and Puppeteer drive a real engine, so their TLS usually matches the bundled browser; differences point to an old bundled build or a modified launch.
  2. Your HTTP library against a browser. Python, Go and Node clients produce their own fingerprints, often with no ALPN or a different cipher order. Sending a browser user agent from such a client is a visible mismatch.
  3. The same client direct and through your proxy. The value should be identical, which confirms the proxy is passing TLS through.

For a capture-based check, FoxIO's repository includes a Wireshark plugin, a Zeek package and Python and Rust implementations that read JA4 values from a packet capture.

What a proxy does and does not change

A proxy sits between your client and the site, but for HTTPS it usually does not touch the handshake.

  • With an HTTP proxy, an HTTPS request starts with a CONNECT. The proxy opens a TCP connection to the site and relays bytes; your client then performs the TLS handshake through that tunnel. The site sees your client's Client Hello, so your JA3 and JA4 are unchanged.
  • With SOCKS5, the same applies: the proxy relays the stream and your client does TLS.
  • Only a proxy that terminates TLS (an inspection proxy that installs its own certificate) produces its own Client Hello. Commercial forward proxies for this kind of work do not.

What the proxy does change is everything below TLS. The site sees the proxy's IP address and network, and the TCP connection to the site is opened by the proxy device, so TCP/IP header details such as initial TTL, window size and option order come from the device's operating system rather than yours. That is the layer covered in TCP/IP fingerprinting and p0f, and it is why some Coronium lines offer an OS fingerprint setting.

Coronium's mobile proxies are relays of this kind: HTTP and SOCKS5 on every line, with the TLS handshake left to your client. To keep the layers consistent, pair a real browser (its own TLS) with a line whose country, carrier and OS profile fit that browser. The broader detection stack is laid out in how websites detect proxies, and browser-side signals in browser fingerprint detection.

Sources and review scope

Sources reviewed October 30, 2026. Vendor documentation, standards and public filings were checked on that date. Coronium sells mobile proxies; the vendors described here were not consulted, and descriptions of their systems rely on what they publish. Code examples ran against local fixtures or vendor test keys. Nothing here is a method for getting past a particular site.

Frequently asked questions

More on bot defenses and proxy errors

Each guide reads one vendor or one symptom from primary documentation and ends with the checks to run.

Related workflows

JA4 fingerprint checker

See the TLS fingerprint your client sends.

TCP/IP fingerprinting and p0f

The operating system layer below TLS.

Browser fingerprint detection

Browser API signals that sit above TLS.

Questions on bots and proxies

Short answers on blocks, fingerprints and proxy errors.

Where to get a dedicated line

A Coronium mobile proxy is one physical device with its own SIM, rented to one customer. It connects over HTTP or SOCKS5, the IP changes by dashboard button, API link or timer, and data is not metered on dedicated lines. Brazil lines are the exception: 20 GB of data a month and a new IP on request only, with no timer. Stock and prices differ by country and carrier.