RUEN

How it works

What is a JA3 fingerprint: TLS fingerprinting, JA4 and what Reality changes

9 min read

TLS encrypts what you send. It does not hide that you connected, and it does not hide how your client asked to connect. The first message of every session, the ClientHello, travels in the clear, because at that point the two sides have no key yet. That message is detailed enough to identify the software that produced it. JA3 and JA4 are the two common ways of writing that description down as a short string.

What the handshake gives away before encryption starts

A ClientHello is a list of everything the client is willing to do. It has to be readable, because the server picks from it. In practice it carries:

  • The versions the client supports, in the supported_versions extension, plus the legacy version field that TLS 1.3 pins at 1.2 so old middleboxes do not choke on it.
  • The cipher suites, in the client's own order of preference.
  • The list of extensions, historically also in a fixed order.
  • Supported groups and key share entries — the classical curves, and increasingly a hybrid post-quantum group such as X25519MLKEM768, which is itself a distinguishing detail. See post-quantum encryption in VPNs.
  • Signature algorithms the client will accept.
  • ALPN — which application protocol it wants, usually h2 or http/1.1.
  • SNI — the host name it is asking for, in plain text unless Encrypted Client Hello is in play.
  • GREASE values (RFC 8701): reserved code points with no meaning, inserted deliberately so that peers and middleboxes are forced to tolerate values they do not recognise instead of ossifying around a fixed shape.

None of that is your data. All of it describes your TLS stack. BoringSSL in Chrome, NSS in Firefox, Apple's own stack on iOS and macOS, Go's crypto/tls, rustls, Java's JSSE and Windows Schannel each emit a ClientHello you can tell apart on sight, and the same library changes its output between versions and build options.

How JA3 works

JA3 was published in 2017 by three engineers at Salesforce — John Althouse, Jeff Atkinson and Josh Atkins — and the name comes from the initials all three share. It takes five fields from the ClientHello: TLS version, cipher suites, extensions, supported groups, EC point formats. It writes their numeric IANA values out, joins the values inside a field with dashes and the fields with commas, drops GREASE entries, and runs MD5 over the result. What you store is 32 hex characters.

JA3S does the same for the server's reply: version, the single cipher chosen, extensions. A JA3/JA3S pair describes a conversation rather than one side of it, which is where the method came from — spotting a malware family talking to its command server without decrypting anything. Network monitoring tools such as Zeek and Suricata log these fields, which is why the format spread.

A fingerprint is not a decryption and not an identification. Nobody reads the traffic. The connection is sorted into a class by its outward shape, the same way you can tell a delivery van from a bicycle without knowing what is inside either.

Why JA3 aged badly and what JA4 changed

Two problems caught up with JA3. The first is extension order. From Chrome 110 in 2023, Chrome permutes the order of extensions in the ClientHello on every connection — deliberately, on the same reasoning as GREASE: if the order never varies, deployed middleboxes start depending on it. Order is part of the JA3 input, so Chrome no longer has a JA3 fingerprint. It has a new one on practically every connection.

The second is MD5. Two nearly identical handshakes hash to unrelated values, so a JA3 is useful only against a lookup table. You cannot compare two of them, group them, or read anything off one.

JA4, published in 2023 by FoxIO, is the answer to both. It sorts the cipher and extension lists before hashing, which cancels the permutation, and it keeps the first part of the fingerprint human-readable. A JA4 looks like t13d1516h2_8daaf6152771_e5627efa2ab1 and splits into three segments:

  • t13d1516h2 — transport (t for TLS over TCP, q for QUIC, d for DTLS); the highest version the client offers, taken from supported_versions (13 for 1.3); whether SNI is present (d for a domain) or absent (i, a bare address); the number of cipher suites (15) and of extensions (16), both counted with GREASE excluded; and the first and last alphanumeric character of the first ALPN value — h2 for HTTP/2, h1 for http/1.1.
  • The second segment is a truncated SHA-256 over the cipher list sorted into hex order.
  • The third is a truncated SHA-256 over the sorted extension list — with SNI and ALPN removed, since those change per site — plus the signature algorithms in their original order.

The practical difference is that you can read the first segment with no database at all, and that JA4 covers fields JA3 ignored, ALPN and signature algorithms among them. It is also one of a family: JA4S for the server side, JA4H for HTTP headers, JA4X for certificates, JA4L for round-trip timing. That matters for the next section, because the interesting question is rarely what one layer says on its own.

Check the licensing before you build any of this into a product. JA4 itself is BSD 3-Clause, like JA3. The rest of the JA4+ family is published under the FoxIO License 1.1, which the project describes as permissive for academic and internal business use but not for monetization.

What a fingerprint identifies, and what it does not

A TLS fingerprint identifies an implementation and its configuration. It does not identify a person. Every device running the same current Chrome build produces the same JA4, and there are a great many of them. On its own the signal carries very little entropy — it puts you in a bucket, and the bucket is enormous.

What makes fingerprints useful to the other side is disagreement between layers. Each of these is a contradiction that no ordinary client produces:

  • The handshake is Go's crypto/tls, the User-Agent header says Chrome on Windows.
  • The handshake is Chrome, but the HTTP/2 SETTINGS frame carries values no Chrome build sends.
  • The handshake matches a Chrome release that stopped shipping a year ago, arriving today.
  • The combination matches no released browser at all — the usual failure mode of tools that patch a browser to look like a different one.
  • The client claims a browser, and then holds one connection open for six hours moving traffic in both directions.

It is also worth being clear about who does this. Most of the JA3 and JA4 collection you will actually meet is anti-bot and anti-fraud work at CDNs, WAFs and payment providers, not censorship. Where network-level filtering does use a fingerprint, it is one input among the destination address, the shape of the flow and active probing, and rarely the decisive one.

The goal is not to look unique — it is to look like everyone else. A heavily patched or unusually configured browser can end up rarer than a stock one and pay for it in CAPTCHAs and blocked requests. Fingerprinting punishes the unusual, whatever the intent behind it.

Why this matters if you run Xray or sing-box

A proxy client opens an outbound TLS connection like anything else. If it lets its runtime build the handshake, the ClientHello says so: Go's stack, or rustls, depending on what the client is written in. By itself that means nothing — plenty of ordinary software is written in Go. It gets interesting in combination: a Go handshake, an SNI naming a consumer website, an address in a hosting network, and a session that behaves nothing like a page load.

uTLS is the standard answer. It is a Go library that assembles a ClientHello from a captured browser profile instead of letting crypto/tls generate one. In Xray this is the fingerprint field in tlsSettings or realitySettings; in sing-box it is the utls block on the outbound TLS settings, with its own enabled and fingerprint fields. Both accept values such as chrome, firefox, safari, ios, android and edge. Current Xray documents chrome as the default and unsafe as the way to fall back to Go's own stack; sing-box uses Chrome when uTLS is enabled with an empty fingerprint, and Go's stack when it is not enabled at all. Defaults have moved between releases, so set the field explicitly and check what your build actually sends rather than assuming.

Three limits on that, all of them real:

  • Profiles age. uTLS reproduces the browser it was captured from. Browsers ship every few weeks; a client that has not been updated in a year presents last year's Chrome, which is itself a rare thing to see on the wire.
  • random and randomized are not automatically safer. The first picks a different real profile per connection, giving your client a distribution no single device produces. The second generates a valid ClientHello that need not correspond to any shipped browser at all, which is the opposite of blending in.
  • Parroting covers the handshake only. Xray's own documentation is explicit that the feature simulates the ClientHello and leaves the rest of the behaviour as Go's. Everything after the first message — HTTP/2 SETTINGS values, flow control, record sizes, session resumption, what happens on an unexpected alert — comes from the code you are actually running. That gap has been the known weak point since The Parrot is Dead (2013); the uTLS work that followed (2019) says so directly, and discrepancies in uTLS itself have kept turning up since, which is why the sing-box documentation carries its own caution about relying on it.

What Reality changes

Reality approaches this from the server end. Rather than making a proxy handshake resemble a browser visiting a plausible site, it arranges for the server to be able to produce a real site's handshake when asked.

  1. 1The client sends a uTLS-shaped ClientHello with SNI set to one of the names the server is configured to accept — a real external site the server can reach — and folds its authentication material into fields that already carry opaque bytes, the X25519 key share and the session ID.
  2. 2The server checks that material. If it does not verify — a scanner, an active probe, someone who pasted the address into a browser — nothing special happens. The connection is relayed to the real target, and the visitor completes a genuine TLS session with the genuine site, its real certificate and its real content. There is no wrong-password response to find, because Reality never answers in its own voice.
  3. 3If it does verify, the server takes the handshake over and returns a temporary certificate signed with a key derived from the shared secret, rather than the target's real chain. Under TLS 1.3 the Certificate message is itself encrypted, so an observer on the path cannot see the substitution, and the client verifies the server against the Reality public key instead of a CA. That is why there is no domain to buy and no certificate to renew.

What that buys, concretely: no certificate of your own visible on the wire, no bare IP address answering with a freshly issued or self-signed certificate, and a truthful answer to active probing — the technique that exposed earlier proxy protocols, and the one documented in detail against Shadowsocks and VMess servers reachable from China. The protocol itself is covered in more depth in VLESS Reality explained.

What Reality does not change

This is the part that gets skipped. Reality fixes the handshake. It does not fix everything a handshake sits inside.

  • SNI is still in the clear. That is by design — the point is that the name survives inspection, not that it is hidden.
  • The address does not match the name. SNI claims a well-known site; the connection goes to a VPS in a hosting network with no relationship to it. That mismatch is cheap to check at scale and is the main practical weakness of most deployments.
  • Flow shape is untouched. A visit to a news site that lasts six hours and moves gigabytes in both directions does not resemble a visit. Padding, timing and volume are separate problems that Reality does not claim to solve.
  • The client fingerprint is still yours. Reality leans on uTLS for the ClientHello, so a stale or badly chosen profile is still the odd one out, whatever the server does.
  • None of this is anonymity. A tunnel changes the network address a site sees and hides traffic from the local network. It does not change who you are to anyone you sign in to, or what your operating system reports about the machine — see why a VPN does not make you anonymous.

Choosing the target is the only lever you have against the second item, and it is a mitigation rather than a fix. A workable target supports TLS 1.3, HTTP/2 and X25519, is not a domain used for redirects, is not fronted by a large CDN whose address ranges are public knowledge, sits near your server rather than on another continent, is something the traffic on that network would plausibly reach, and is not the same domain every other user of the same tool copied out of the same tutorial.

How to check what your own client looks like

  1. 1For a browser, open a TLS inspection page such as browserleaks.com/tls or tlsfingerprint.io. You get the parsed ClientHello — ciphers, extensions, groups, ALPN — along with the JA3 and JA4 the server computed.
  2. 2For a proxy client, understand what those pages measure. Traffic sent through a tunnel reaches the site as a handshake generated inside the tunnel, normally by your browser. The handshake that matters for detection is the outer one, from client to server, and no website can see it.
  3. 3Capture the outer handshake yourself. On Linux: sudo tcpdump -i any -w hello.pcap 'tcp port 443', then connect, stop the capture and read the ClientHello in Wireshark or with one of the open-source JA4 tools. On macOS or BSD, name the real interface instead of any.
  4. 4Capture a real browser on the same machine the same way and put the two side by side. Version, cipher and extension counts, supported groups and ALPN are enough to see whether the client passes as a browser or merely claims to.
  5. 5Check the server from an unrelated address: openssl s_client -connect <server-ip>:443 -servername <target> </dev/null should end in the target site's genuine certificate, with nothing in the exchange that hints at your setup.
  6. 6Look up the ASN and owner of your server address next to the site you named as the target. If that pairing would look implausible to someone comparing the two, pick a different target.

The short version: a ClientHello describes your software in the open, JA3 and JA4 are just compact ways to write that description down, and detection almost always comes from layers that disagree rather than from a single hash. uTLS makes the client's first message ordinary; Reality makes the server's answer to a stranger genuine. Everything outside the handshake — the address you connect to, how long you stay, how much you move — is still yours to think about. For the transport this rides on, see VLESS Reality versus Hysteria2.

Frequently asked questions

What is a JA3 fingerprint, exactly?

It is an MD5 hash of five fields taken from the ClientHello — TLS version, cipher suites, extensions, supported groups and EC point formats — written out as numbers in the order the client sent them. The result is a 32-character string that identifies the TLS library and configuration behind a connection without decrypting anything. JA3S is the same idea applied to the server's reply.

Can JA3 or JA4 identify me personally?

No. A TLS fingerprint describes software, not a person, and a current browser build produces a fingerprint shared with every other device running that build. It becomes useful to an observer only in combination with other signals — the destination, the shape of the traffic, and above all contradictions between layers, such as a handshake that says Go and a header that says Chrome.

Does a VPN change my TLS fingerprint?

Not the one websites see. Your browser builds its own ClientHello and that travels through the tunnel unchanged, so a fingerprint test page reports the browser either way. What the tunnel adds is a second, outer handshake from the VPN client to the server, which has a fingerprint of its own. That is the one uTLS profiles are for, and it is visible to your network rather than to the sites you visit.

Why does my JA3 change on every page load?

Because Chrome permutes the order of TLS extensions in the ClientHello, a behaviour introduced in Chrome 110. Extension order is part of the JA3 input, so the hash moves on essentially every connection. This is deliberate — it stops middleboxes from ossifying around a fixed layout — and it is one of the reasons JA4 sorts the lists before hashing.

Does Reality hide the fact that I am using a proxy?

It removes the handshake-level tells: no certificate of your own on the wire, no unusual TLS stack on the client if uTLS is configured, and a genuine website in response to probing. It does not hide that a TLS connection happened, does not resolve the mismatch between the name in the SNI and the owner of the server address, and does not change how much traffic flows or for how long. Treat it as removing the easy signals, not as invisibility.