A clean, dependency-free Go library implementing the FoxIO JA4+ network fingerprint suite: JA4, JA4S, JA4H, JA4L, JA4T, JA4X and JA4SSH.
Every fingerprint is a pure function over already-parsed inputs (a ClientHello
struct, a *x509.Certificate, TCP SYN fields, timing values, …), so the library
is I/O-free and trivially testable. Small bounds-checked byte parsers are
included for the TLS ClientHello and ServerHello. Standard library only — no
third-party dependencies.
go get github.com/North-web-dev/ja4plus-go
This is the broad, standalone library for the full JA4+ suite. It is independent of
North-web-dev/ja4-guard(which is a JA4/JA4H implementation tuned specifically for inbound request guarding).
JA4+ is a suite of locality-preserving a_b_c fingerprints by FoxIO, both human-
and machine-readable, used for threat-hunting, malware/CDN/scanner detection and
actor grouping. Each sub-fingerprint targets one protocol:
| Fingerprint | What it fingerprints | Spec |
|---|---|---|
| JA4 | TLS client ClientHello (version, SNI, cipher/extension counts, ALPN; sorted cipher hash; sorted extension + sigalg hash) | JA4.md |
| JA4S | TLS server ServerHello (version, extension count, ALPN, chosen cipher; extension hash) | JA4S |
| JA4H | HTTP client request (method, version, cookie/referer presence, header count, Accept-Language; header hash; cookie hashes) | JA4H.md |
| JA4L | Light distance / latency — one-way latency (µs) + observed TTL, client & server variants | JA4L |
| JA4T | TCP SYN fingerprint (window size, option kinds, MSS, window scale) | JA4T |
| JA4X | X.509 certificate (issuer/subject RDN OIDs, extension OIDs — OIDs only, never values) | JA4X |
| JA4SSH | SSH traffic sample (mode packet lengths, packet counts, bare ACK counts) | JA4SSH |
GREASE values (RFC 8701) are filtered everywhere the spec requires.
Every algorithm is checked against FoxIO's own documented vectors and/or real
captured traffic. No hashes are fabricated — each expected value is either
quoted from the spec, reproduced from a real capture, or computed from the
documented algorithm. See the *_test.go files and testdata/.
| Fingerprint | Status | How it is verified |
|---|---|---|
| JA4 | ✅ Verified (spec vector + independent cross-check) | The three cipher/extension hash vectors quoted verbatim from JA4.md match exactly. A real curl 7.81.0 ClientHello captured on the wire (testdata/curl_clienthello.bin) parses to t13d3113h2_e8f1e7e78f70_ce5650b735ce, identical to what tls.peet.ws computes for the same client. All 6 ALPN label vectors from JA4.md match. GREASE filtering tested. |
| JA4S | ✅ Verified (real capture + documented algorithm) | A real TLS 1.3 ServerHello captured from example.com (testdata/example_serverhello.bin) → t130200_1302_234ea6891581, matching the FoxIO JA4S algorithm. FoxIO publishes no ServerHello inputs for its example hashes, so a bit-for-bit match against a published malware JA4S is not reproducible. |
| JA4H | ✅ Verified (documented algorithm) | Full a_b_c_d output matched against the FoxIO ja4h.py reference algorithm on a known request, including cookie sorting, header exclusions, HTTP/2 pseudo-header handling and Accept-Language formatting. |
| JA4X | ✅ Verified (published vector anchor + real cert) | oidToHex matches 6 known DER OID encodings. A single-CN subject hashes to 7022c563de38, which is exactly the subject hash FoxIO publishes for Async/Quasar/BitRAT (7022c563de38_7022c563de38_…). End-to-end on a real example.com certificate. |
| JA4T | ✅ Verified (published vector) | FoxIO's published Windows 11 example 64240_2-1-3-1-1-4_1460_8 reproduced from its documented SYN fields. Pure function — needs TCP SYN capture as input. |
| JA4L | ✅ Verified (documented example) | The one-way-latency math reproduces the spec diagram's 5191 for a 10382 µs round trip. Pure function — needs measured handshake timestamps + TTL. |
| JA4SSH | ✅ Verified (published vectors) | FoxIO's reverse-shell c76s76_c71s59_c0s70 and interactive c36s36_c51s80_c69s0 examples reproduced. Pure function — needs per-sample SSH packet stats. |
FoxIO's published materials are internally inconsistent on how an empty hashed
component renders (older docs/examples show 000000000000; the current ja4x.py
would emit the SHA-256 of the empty string). This library uses the suite-wide
000000000000 sentinel for every empty hashed list (JA4 ciphers/extensions,
JA4S extensions, JA4X RDNs/extensions), matching JA4's documented behaviour and
FoxIO's published fingerprint examples (e.g. Sliver 000000000000_…, Qakbot
…_000000000000). This is documented here so the choice is explicit, not hidden.
import ja4plus "github.com/North-web-dev/ja4plus-go"
ch, err := ja4plus.ParseClientHello(clientHelloBytes) // full record or bare handshake
if err != nil { /* ... */ }
fmt.Println(ja4plus.JA4(ch)) // t13d3113h2_e8f1e7e78f70_ce5650b735ce
fmt.Println(ja4plus.JA4Raw(ch)) // the "_r" form: cleartext sorted lists
// Or populate ClientHello yourself from another TLS library:
ch = &ja4plus.ClientHello{
SupportedVersions: []uint16{0x0304},
CipherSuites: []uint16{0x1301, 0x1302},
Extensions: []uint16{0x0000, 0x0010, 0x000d},
HasSNI: true, ServerName: "example.com",
ALPNProtocols: []string{"h2"},
}sh, _ := ja4plus.ParseServerHello(serverHelloBytes)
fmt.Println(ja4plus.JA4S(sh)) // t130200_1302_234ea6891581req := &ja4plus.HTTPRequest{
Method: "GET",
Version: "HTTP/1.1",
Headers: []ja4plus.Header{ // in wire order, original casing
{"Host", "example.com"},
{"User-Agent", "curl/7.81.0"},
{"Accept", "*/*"},
{"Accept-Language", "en-US,en;q=0.9"},
{"Referer", "http://example.com"},
{"Cookie", "b=2; a=1"},
},
}
fmt.Println(ja4plus.JA4H(req)) // ge11cr04enus_8ddaef5d77af_1eb7c54d5283_06beefe2b477cert, _ := x509.ParseCertificate(der)
fmt.Println(ja4plus.JA4X(cert)) // <issuerHash>_<subjectHash>_<extHash>
// or straight from ordered OID lists:
ja4plus.JA4XFromOIDs(issuerOIDs, subjectOIDs, extensionOIDs)fmt.Println(ja4plus.JA4T(ja4plus.TCPSYN{
WindowSize: 64240,
Options: []uint8{2, 1, 3, 1, 1, 4}, // option kinds, in order
MSS: 1460,
WindowScale: 8,
})) // 64240_2-1-3-1-1-4_1460_8 (Windows 11)// JA4L-S: client SYN time, server SYN-ACK time, server TTL
fmt.Println(ja4plus.JA4L(synTime, synAckTime, serverTTL)) // "<µs>_<ttl>"
// JA4L-C: server SYN-ACK time, client ACK time, client TTL
fmt.Println(ja4plus.JA4L(synAckTime, ackTime, clientTTL))
fmt.Println(ja4plus.EstimatedHopCount(42)) // 22fmt.Println(ja4plus.JA4SSH(ja4plus.SSHSample{
ModeClientPayloadLen: 76, ModeServerPayloadLen: 76,
ClientPackets: 71, ServerPackets: 59,
ClientAcks: 0, ServerAcks: 70,
})) // c76s76_c71s59_c0s70 (reverse shell)- JA4, JA4S and JA4H are spoofable. They are derived from fields the remote party controls and can be deliberately altered (cipher/extension ordering is sorted away precisely because clients randomise it). Use JA4+ for grouping and threat-hunting, not for authentication.
- JA4L, JA4T and JA4SSH require data this library cannot collect itself. JA4L needs measured handshake timestamps and the observed TTL; JA4T needs the fields of a captured TCP SYN; JA4SSH needs per-sample SSH packet statistics (FoxIO samples every 200 packets). These are provided as pure functions over those inputs — you feed them from your capture pipeline.
- JA4TScan (the active-scan 5th field of JA4T) and DHCP/NTP fingerprints are out of scope.
- Where a component cannot be matched bit-for-bit against a FoxIO-published fingerprint (because FoxIO does not publish the raw inputs for its examples), it is verified against the documented algorithm and/or real captures, and the status table above says so plainly.
The JA4+ fingerprinting methods were created by John Althouse / FoxIO, LLC. All specification credit belongs to FoxIO; this repository is an independent Go implementation of their public spec. See LICENSE for the full licensing picture: the Go code here is MIT, while the JA4+ methods remain FoxIO's IP (JA4 is BSD-3-Clause; the rest are patent-pending under the FoxIO License 1.1). "JA4" and "JA4+" are trademarks of FoxIO, Inc.
If this library saved you time, a ⭐ on the repo is hugely appreciated — it helps others find it.
| Coin | Address |
|---|---|
| BTC | bc1q0sfghv7yfl594gjurhyhlhdgm0dtwh4dkjha36 |
| ETH / USDT-ERC20 / BNB-BEP20 | 0xB827b5a8B6c5111946c5D872933b2C23484E87dE |
| SOL | 3ch5aHrPp3CierhiNXmqmtFH2dMHh6Gfv7a97KtLMMCh |
| USDT-TRC20 / TRX | TUAyz7TjgJdFVD2oFHWUUTNrzzAnBRgySG |
| TON | UQAJmYVuR3hh02etjaPKJF-h6jcpdSnq9ZIFvI64NEskS6tx |