What Is JA3 Fingerprinting?

JA3 is a TLS client fingerprint created at Salesforce in 2017. It reads five fields from the TLS ClientHello (version, cipher suites, extensions, elliptic curves and elliptic curve point formats), writes their decimal values into one string, and hashes that string with MD5 into a 32-character fingerprint such as ada70206e40642a3e4461f35503241d5.

The hash describes the TLS library and configuration that opened a connection. It does not describe a person or a device. John Althouse, who created JA3 with Jeff Atkinson and Josh Atkins, put the limit plainly: "You could think of JA3 as the TLS equivalent of the User-Agent string. Just because one piece of software or malware has a particular string doesn't mean it will always be unique to that software." The method built on Lee Brotherston's 2015 research into TLS fingerprinting. Salesforce no longer maintains it, yet JA3 values still appear in WAF logs and bot-management fields. This page covers how a JA3 is built, how to read one, and what has changed since 2017.

How is a JA3 hash built?

A JA3 is built from five ClientHello fields, always in the same order. Each value is written as a decimal number. Values inside a field are joined with a hyphen, fields are joined with a comma, and the finished string is hashed with MD5. GREASE values are removed before anything is written.

The Salesforce README describes the method in one sentence: "JA3 gathers the decimal values of the bytes for the following fields in the Client Hello packet; SSL Version, Accepted Ciphers, List of Extensions, Elliptic Curves, and Elliptic Curve Formats. It then concatenates those values together in order, using a "," to delimit each field and a "-" to delimit each value in each field."

The field order is fixed as SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat.

PositionFieldWhat it holdsShape
1SSLVersionThe version number in the ClientHelloOne decimal value
2CipherThe cipher suites the client offers, in the order sentHyphen-separated list
3SSLExtensionThe extension types the client sends, in the order sentHyphen-separated list
4EllipticCurveThe elliptic curves the client supportsHyphen-separated list
5EllipticCurvePointFormatThe elliptic curve point formats the client supportsHyphen-separated list

A field with nothing in it stays in the string as an empty slot. The README: "If there are no SSL Extensions in the Client Hello, the fields are left empty." Its second example shows the result, with three trailing commas and no values after the cipher list:

769,4-5-10-9-100-98-3-6-19-18-99,,, --> de350869b8c85de67a350c8d186f11e6

The final step is MD5. Althouse gave two reasons for it: "We MD5 hash because there is no limit to how many ciphers or extensions can be added to the Client Hello or Server Hello respectively, and our rule of thumb is that if the fingerprint cannot fit in a tweet, then it's too long. We also use MD5 so the JA3 method can be more easily integrated into existing technologies."

Why does JA3 remove GREASE values?

GREASE (Generate Random Extensions And Sustain Extensibility) is a set of reserved TLS values that clients advertise on purpose, to make sure peers correctly handle values they do not recognize. RFC 8701, published in January 2020, defines it. Because clients pick the values at random, JA3 drops them to keep one client on one hash.

RFC 8701 reserves 16 values for each type. For cipher suites the pattern is {0x0A,0x0A}, {0x1A,0x1A} and so on up to {0xFA,0xFA}. For extensions, named groups, signature algorithms and versions it is 0x0A0A, 0x1A1A and so on up to 0xFAFA. The RFC states that "Implementations advertising GREASE values SHOULD select them at random."

Left in, those random values would change the string from one connection to the next. The JA3 README, which cites the earlier Internet-Draft rather than the RFC, explains the fix: "JA3 ignores these values completely to ensure that programs utilizing GREASE can still be identified with a single JA3 hash." Implementations filter the lists before writing the string. The open-source Go library fingerproxy, for example, holds the reserved values in a lookup table and applies it to ciphers, extensions and supported groups, with the comment "Ciphers, extensions and elliptic curves, should be filtered so GREASE values are not added to the ja3 digest". Since JA3 writes decimal, a GREASE value would appear as a number from 2570 (0x0A0A) to 64250 (0xFAFA). A JA3 string that contains one of the reserved values was built by an implementation that skipped the filter.

How do you read a JA3 string?

Split it on commas into five fields, then split each field on hyphens. The first field is the version, the second the ciphers, the third the extensions, the fourth the curves, and the fifth the point formats. The worked example from the Salesforce README produces ada70206e40642a3e4461f35503241d5.

769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0 --> ada70206e40642a3e4461f35503241d5
FieldValue in the exampleReading
SSLVersion769769 in decimal is 0x0301, the value for TLS 1.0
Cipher47-53-5-10-49161-49162-49171-49172-50-56-19-412 cipher suites, in the order offered
SSLExtension0-10-113 extensions, in the order sent
EllipticCurve23-24-253 curves
EllipticCurvePointFormat01 point format

The hash can be reproduced from the string alone. The string is hashed exactly as written, with no trailing newline:

printf '%s' '769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0' | md5
ada70206e40642a3e4461f35503241d5

Treat the example as a teaching string. It comes from the README, and the README does not say which client produced it, so it should not be read as the fingerprint of any current browser.

Two practical points follow from the format. A JA3 hash cannot be read on its own, because MD5 hides every field, so debugging a match or a mismatch needs the pre-hash string. And any change to the string, including a change in the order of two ciphers or two extensions, produces a different hash.

What is JA3S?

JA3S is the server-side counterpart. It fingerprints the ServerHello a server sends back, using three fields: version, the cipher the server accepted, and the list of extensions. The Salesforce README describes it as "JA3 for the Server side of the SSL/TLS communication and fingerprints how servers respond to particular clients."

The field order is SSLVersion,Cipher,SSLExtension. Althouse's write-up gives this example string:

769,47,65281-0-11-35-5-16

The cipher field holds one value, the accepted cipher, where JA3 holds the full list the client offered. An empty example, 769,47,, shows a ServerHello with no extensions. Neither source publishes the MD5 of the example string.

JA3S is most useful as a pair with JA3. Because it records how a server responds to a particular client, the two hashes together narrow a connection more than either alone. The README lists pairs as examples: Trickbot with JA3 6734f37431670b3ab4292b8f60f29984 and JA3S 623de93db17d313345d7ea481e7443cf, and Emotet with JA3 4d7a28d6f2263ed61de88ca66eb011e3 and JA3S 80b3a14bccc8598a1f3bbe83e71f735f. It adds a caveat that applies to every published value: "these are just examples, not indicative of all versions ever."

Why did JA3 stop being stable for Chrome?

Chrome randomizes the order of its ClientHello extensions. Chrome's feature entry lists this as enabled by default from Chrome 110, released to stable in February 2023. JA3 writes extensions in the order sent. One Chrome build therefore produces many different JA3 hashes, and the hash no longer maps to one browser version.

Chrome's feature entry, created on 25 May 2022, lists the change as enabled by default from milestone 110 on desktop, Android and WebView. Chrome 110 reached early stable on 1 February 2023. The entry's summary: "Randomize the order of TLS ClientHello extensions, to reduce potential ecosystem brittleness. This change is only visible to HTTPS server implementers."

The purpose was to keep servers from depending on a fixed order. The explainer says so directly: "Using a fixed extension order can encourage server implementers to fingerprint specific browsers and then assume specific implementation behavior." It also states the scope: "This is not designed to prevent all fingerprint of TLS stacks." The order is random within one constraint: "The browser will randomly order extensions, subject to the pre_shared_key constraint in the RFC."

The behavior remains in place. The Chromium networking code on the main branch, read on 14 November 2026, still contains SSL_set_permute_extensions(ssl_.get(), 1);, the BoringSSL call that turns permutation on. BoringSSL documents that permutation is "only implemented for the ClientHello."

The effect on JA3 is direct. The cipher list, curve list and point formats stay the same between connections, but the third field is reshuffled, so the MD5 changes. Neither Chrome's feature entry nor the explainer says how many distinct JA3 values a single Chrome build generates. As a result, a JA3 allowlist or blocklist entry written for one Chrome hash matches only some of that browser's connections.

Is JA3 still maintained?

No. Salesforce archived the salesforce/ja3 repository, and as of 14 November 2026 its last push was on 1 May 2025. The README states that the project is no longer actively maintained and points readers to JA4, maintained by JA3's original creator at FoxIO.

The README's own wording: "JA3 was invented at Salesforce in 2017. However, the project is no longer being actively maintained by Salesforce. Its original creator, John Althouse, maintains the latest in TLS client fingerprinting technology at FoxIO-LLC." The repository still describes JA3 as "a standard for creating SSL client fingerprints in an easy to produce and shareable way."

Public lists of known JA3 values have aged in the same way. The SSLBL JA3 list at abuse.ch describes itself as "a list of malicious JA3 fingerprints identified by SSLBL", built from more than 25,000,000 PCAPs generated by malware samples. As read on 14 November 2026, the newest listed entry is dated 3 August 2021 (AsyncRAT), followed by entries from 27 May 2021 (BitRAT) and 9 April 2020 (TrickBot). The page carries its own warning: "These fingerprints have not been tested against known good traffic yet and may cause a significant amount of FPs!"

Where does JA3 still show up?

Major edge and WAF products still expose JA3, usually next to JA4. AWS WAF added JA3 matching on 27 September 2023 and JA4 on 6 March 2025. Cloudflare exposes both JA3 and JA4 fields to Enterprise customers with Bot Management. The Salesforce README also lists AWS Network Firewall, Azure Firewall and Google Cloud among the products that added JA3.

AWS WAF. The September 2023 announcement: "AWS WAF now supports JA3 match, enabling customers to inspect incoming requests' JA3 fingerprints." It describes the value as a "32-character hash fingerprint of the TLS Client Hello packet of an incoming request." The developer guide, read on 14 November 2026, sets a limit: "JA3 fingerprint inspection is available only for Amazon CloudFront distributions and Application Load Balancers." The March 2025 update added JA4 and made both JA3 and JA4 usable as aggregation keys in rate-based rules.

Cloudflare. The JA3 and JA4 documentation, last updated 6 May 2026, states: "JA3 and JA4 fingerprints are only available to Enterprise customers who have purchased Bot Management." Both fields are null for traffic that is not TLS.

A JA3 value in one of these products is the same 32-character MD5 described above, computed from the ClientHello the vendor's edge received.

How does JA3 compare to JA4?

JA4, published by FoxIO in September 2023, was built as JA3's replacement. It sorts ciphers and extensions before hashing, so random ordering no longer changes the result, and it keeps a readable prefix instead of one opaque digest. FoxIO's launch post describes JA4+ as "replacing the JA3 TLS fingerprinting standard from 2017."

FoxIO links the sorting to the Chrome change: "Earlier in 2023, Google updated Chromium browsers to randomize their extension ordering. ... sorting the extensions gets around this and adding in Signature Algorithms preserves uniqueness." Cloudflare's documentation describes the effect: "JA4 improves on JA3 by sorting ClientHello extensions, which reduces the number of unique fingerprints for modern browsers and makes grouping easier."

JA3JA4
HashOne MD5 digest, 32 charactersThree sections joined by underscores, the second and third each a SHA-256 hash truncated to 12 characters, such as t13d1516h2_8daaf6152771_b186095e22b6
OrderingCiphers and extensions hashed in the order sentCiphers and extensions sorted in hex order before hashing
ReadabilityNothing readable without the pre-hash stringFirst section shows protocol, TLS version, SNI, cipher and extension counts, and ALPN
LicenceBSD 3-ClauseJA4 itself BSD 3-Clause. The other JA4+ methods are under the FoxIO License 1.1
MaintainedRepository archived, last push 1 May 2025 (as of 14 November 2026)Repository active, last push 12 November 2026 (as of 14 November 2026)

For a full walk through each JA4 field, see JA4 and JA4T fingerprints, field by field.

Can a bot fake a JA3 fingerprint?

Yes. A JA3 is computed from bytes the client chooses to send, and several open-source tools exist to change those bytes. Some copy a real browser's ClientHello. Others randomize it so that each connection can carry a new hash.

uTLS, a fork of Go's crypto/tls, "provides ClientHello fingerprinting resistance, low-level access to handshake, fake session tickets and some other features." Its README also notes that "Randomized Fingerprints are supposedly good at defeating blacklists, since those fingerprints have random ciphersuites and extensions in random order." curl-impersonate is a curl build that "can impersonate the four major browsers: Chrome, Edge, Safari & Firefox." curl_cffi, which builds on curl-impersonate, states that it "can impersonate browsers' TLS/JA3 and HTTP/2 fingerprints." Randomizing cipher order to dodge JA3, which FoxIO calls "cipher stunting," is one of the tactics JA4's sorting was designed to blunt.

Rotating IP addresses does not change a JA3. A proxy that forwards the connection changes the source address the server sees, but the ClientHello still comes from the client's own TLS stack. A bot cycling through residential proxy exit addresses presents the same JA3 from each one, unless it also changes its TLS client. That makes JA3 useful as what Althouse called "a pivot point for analysis," while a match alone does not prove what the client is.

Frequently Asked Questions

A JA3 fingerprint is a 32-character MD5 hash of five fields from a TLS ClientHello: the version, the offered cipher suites, the extensions, the elliptic curves and the elliptic curve point formats. The values are written as decimal numbers, joined with hyphens inside a field and commas between fields, then hashed. Salesforce created the method in 2017.

The first field of a JA3 string is the version number from the ClientHello, written in decimal. 769 is 0x0301 in hexadecimal, the value for TLS 1.0. It appears in the example strings of the original Salesforce README.

JA3 fingerprints the client's ClientHello using five fields. JA3S fingerprints the server's ServerHello using three: version, the cipher the server accepted, and the list of extensions. JA3S records how a server responds to a particular client, so the pair narrows a connection more than either hash alone.

Chrome randomizes the order of the extensions in its ClientHello, enabled by default from Chrome 110 (February 2023). JA3 includes the extensions in the order sent, so the hash changes between connections even though the browser has not changed. The Chromium source on the main branch still enabled this as read on 14 November 2026.

Yes, though it is no longer maintained. Salesforce archived the repository, with its last push on 1 May 2025, and the README points to JA4. As of 14 November 2026, AWS WAF (on CloudFront and Application Load Balancers) and Cloudflare (for Enterprise Bot Management customers) expose JA3 values alongside JA4.

Yes. A JA3 identifies a TLS library and its configuration, so different software built on the same stack with the same settings can share a value. Its creator compared it to the User-Agent string and wrote that a given string "doesn't mean it will always be unique to that software."

No, as long as the proxy forwards the connection rather than opening a new TLS session of its own. The proxy changes the source IP address, while the ClientHello still comes from the client's TLS stack. The same client presents the same JA3 from every exit address unless it changes its TLS client.

Trueguard Basic is free.

Start identifying visitors and signals right away, for free

Sign up for free

No credit card required.

trueguard-logo© 2026 Trueguardinfo@trueguard.io