JA4+ is a family of fingerprints for how a client (or server) speaks at each layer: TLS, HTTP, TCP, latency, certificate, SSH, QUIC. It replaced JA3 because it sorts lists before hashing - so it survives the per-connection TLS extension shuffling Chrome shipped in 2023 that made JA3 blocklists useless. Universal rules below apply to every member: every hash is sha256[:12] (12 hex chars), GREASE (RFC 8701) is stripped everywhere before counting/hashing, an empty list hashes to 000000000000, counts are 2-digit capped at 99, and lists are sorted only where the sender could reorder them. Hover the JA4 string below.
The flagship. Format a_b_c = readable prefix, then two hashes. Real Chrome below - hover a segment for how it's derived.
tls12.pcap the count is 15 while the hashed list holds 13 - the difference is exactly SNI + ALPN. Get this wrong and every fingerprint is off.c is half-sorted. The extension list is sorted (an evader can reorder extensions), but the signature-algorithm list appended after the last _ is kept in wire order (it's an OS/stack tell). Version comes from the supported_versions extension (highest non-GREASE), not the legacy version field.JA4S <proto><ver><ext#><alpn>_<chosen-cipher>_<exthash> - fingerprints the server's TLS stack + its per-connection choices. Clusters C2 servers sharing a config.
JA4X <issuerhash>_<subjecthash>_<exthash> - hashes the certificate's OIDs (field types), not their values, so tooling that randomizes CN/org but not cert shape still collides.
Format a_b_c_d. Fingerprints the HTTP client by method, header set + order, cookies, and language.
| seg | value | derivation |
|---|---|---|
| ge 11 | method + version | first 2 chars of method (ge=GET), 11=HTTP/1.1 (20=h2, 30=h3) |
| c / n | cookie · referer | flags: Cookie header present? Referer present? |
| 06 | header count | headers excluding Cookie + Referer, cap 99 |
| 0000 | Accept-Language | first 4 alnum of the first Accept-Language (en-US -> enus); 0000 if none |
| _b | header hash | sha256[:12] of header names in WIRE ORDER, minus Cookie/Referer/h2 pseudo-headers |
| _c _d | cookie hashes | sha256[:12] of sorted cookie names, then sorted name=value pairs; 000000000000 if none |
net/http, most stacks) destroys order and computes a wrong JA4H. You must read the raw request head - see the "from the wire" point on the Spoof tab.JA4T <window>_<option-kinds>_<mss>_<wscale> fingerprints the OS TCP stack. The SYN is consumed by the kernel before accept() returns, so a normal server can't see it - eBPF recovers it off the wire.
Ground truth: Windows 64240_2-1-3-1-1-4_1460_8 · Ubuntu 65535_2-4-8-1-3_1460_8 · macOS 65535_2-1-3-1-1-8-4-0-0_1460_6. Windows omits timestamps (opt 8); an MSS below 1460 betrays a tunnel/VPN/proxy in front of the host.
JA4L <latency_us>_<ttl> - one-way latency estimate (half the handshake delta, microseconds) + IP TTL / IPv6 hop-limit (64 Linux/mac, 128 Windows, 255 gear, minus hops).
JA4SSH reads an encrypted SSH session's behaviour over a 200-packet window - it fingerprints activity, not just identity. c<len>s<len>_c<pkts>s<pkts>_c<acks>s<acks>.
c69s0); a reverse shell flips the ACKs to the server (c0s70) because the server is the one typing. Same identity, opposite direction - visible without decrypting a byte of payload.JA4 q is identical to JA4, with protocol char q, but the ClientHello lives inside the encrypted QUIC Initial.
JA3 hashed the cipher + extension lists in wire order. In early 2023 Chrome began randomizing TLS extension order per connection (default ~Chrome 110). With ~15! orderings, every Chrome connection produced a different JA3 - blocklists on the internet's most common client became meaningless, and salesforce/ja3 was archived.
a segment is greppable before you touch a hash; JA3 was one opaque MD5Forging gets harder the lower you go, because control moves from app to library to kernel:
| member | forge | how |
|---|---|---|
| JA4H (HTTP) | trivial | client owns method/headers/order - just write bytes |
| JA4 (TLS) | moderate | drive cipher/ext/curve/sigalg sets via uTLS; easier than JA3 (need the right set, not order) |
| JA4T (TCP) | hard | no per-conn socket knob; raw-packet the SYN or rewrite it on egress with eBPF (order included) |
| JA4L (latency) | can't | round-trip physics; TTL you can set, timing you'd have to actually be there |
tls.ClientHelloInfo, a header map) is lossy and wrong: the lib strips GREASE, hides extension order, drops what it doesn't model, and a header map destroys JA4H order. An honest fingerprinter sits at the wire (proxy / sensor / eBPF), then replays the untouched bytes into the stack.Cloudflare JA4 Signals (hourly behavioural aggregates keyed on JA4; WAF field cf.bot_management.ja4) - and Cloudflare states plainly that fingerprints are easily spoofed, treating JA4 as a population join key, not an identity. Also AWS CloudFront/WAF, Fastly, Akamai, F5, HAProxy; analysis in Wireshark, Zeek (official package), Suricata (ja4.hash), Censys. Licensing: JA4 is BSD-3; the rest of JA4+ is FoxIO License 1.1 (research/internal, not for monetization).