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.
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.
"""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:
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.
| Part | Example | Meaning |
|---|---|---|
| Transport | t | t for TLS over TCP, d for DTLS, and a separate letter for the newer datagram transport browsers use for HTTP over that protocol |
| TLS version | 13 | Highest version offered, taken from the supported_versions extension when present |
| SNI | d | d if a server name is present, i if the client connected to a bare IP |
| Cipher count | 15 | Number of cipher suites, GREASE excluded |
| Extension count | 16 | Number of extensions, GREASE excluded |
| ALPN | h2 | First and last character of the first ALPN value, 00 if none |
| Cipher hash | 8daaf6152771 | First 12 hex characters of SHA-256 over the sorted cipher list |
| Extension hash | e5627efa2ab1 | First 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:
- 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.
- 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.
- 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.
- RFC 8446: The Transport Layer Security Protocol Version 1.3
Client Hello structure.
- RFC 8701: Applying GREASE to TLS Extensibility
Why browsers send reserved placeholder values.
- Salesforce JA3 repository
Method description; archived May 1, 2025.
- FoxIO: JA4 technical details
Specification and the worked example used in our script.
- FoxIO: JA4+ repository
Family members and licensing.
- Chrome Platform Status: TLS ClientHello extension permutation
Enabled by default in Chrome 110.
- BoringSSL commit adding extension permutation
Per-connection shuffling, pre-shared key kept last.
- Fastly: JA4 fingerprinting in Bot Management
February 2025 changelog.
- Cloudflare: JA3 and JA4 fingerprints
Availability and JA4 signals.
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
See the TLS fingerprint your client sends.
The operating system layer below TLS.
Browser API signals that sit above TLS.
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.