Documentation
¶
Overview ¶
Package ja3 computes the JA3 TLS-client fingerprint from a captured ClientHello — the fingerprint format used across IDS / threat-intel tooling (Suricata, Zeek, every major EDR) to identify a TLS client (and the malware family behind it) independent of IP or SNI.
JA3 (Althouse et al., Salesforce) concatenates five ClientHello fields —
SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat
— values dash-joined, fields comma-joined, then takes the MD5. GREASE values (RFC 8701) are removed from the cipher, extension, and curve lists so a client that randomises GREASE still fingerprints to one hash. This package implements that algorithm over the raw ClientHello wire bytes.
Wrap-vs-native: native — a bounds-checked walk of the TLS record / handshake / ClientHello structure (RFC 5246 §7.4.1.2) + stdlib crypto/md5. No new go.mod dependency. The field extraction and GREASE filtering are pinned against the Salesforce reference implementation (pyja3) on a real openssl ClientHello and a hand-built GREASE-bearing one, and the string→MD5 step against the two worked examples in the JA3 spec.
Scope: JA3 (client). JA3S (the server-side ServerHello variant) is detected and reported as not-yet-supported rather than mis-fingerprinted.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Hello ¶ added in v0.651.0
type Hello struct {
LegacyVersion int
CiphersRaw []uint16 // in offer order, GREASE retained
ExtOrder []uint16 // extension types in appearance order, GREASE retained
Curves []int // supported_groups (ext 10), GREASE removed
PointFormats []int // ec_point_formats (ext 11)
SNI string
SNIPresent bool
ALPN []string // application_layer_protocol_negotiation (ext 16)
SupportedVersions []uint16 // supported_versions (ext 43), GREASE retained
SigAlgs []uint16 // signature_algorithms (ext 13), in order, GREASE retained
}
Hello is the parsed view of a ClientHello that both JA3 and JA4 derive from. Cipher and extension lists retain GREASE (each fingerprint filters as its own spec dictates).
type JA4Result ¶ added in v0.651.0
type JA4Result struct {
// JA4 is the fingerprint: ja4_a_ja4_b_ja4_c.
JA4 string `json:"ja4"`
// JA4R is the raw (un-hashed) form: a_<ciphers>_<exts>_<sigalgs>, useful
// for debugging and exact cross-checking against reference tooling.
JA4R string `json:"ja4_r"`
// A, B, C are the three sections.
A string `json:"ja4_a"`
B string `json:"ja4_b"`
C string `json:"ja4_c"`
// TLSVersion is the negotiated TLS version label (e.g. "13", "12", "10").
TLSVersion string `json:"tls_version"`
// SNI / ALPN are surfaced for context.
SNI string `json:"sni,omitempty"`
ALPN string `json:"alpn,omitempty"`
// Note carries interpretation guidance.
Note string `json:"note,omitempty"`
}
JA4Result is the outcome of a JA4 computation.
func JA4Decode ¶ added in v0.651.0
JA4Decode accepts a ClientHello as hex (a full TLS record or a bare handshake) and returns its JA4 fingerprint.
func JA4FromClientHello ¶ added in v0.651.0
JA4FromClientHello computes the JA4 (FoxIO JA4.md) from raw ClientHello bytes. Scope: TLS-over-TCP (protocol "t"); QUIC/DTLS framing is not unwrapped here.
type JA4SResult ¶ added in v0.652.0
type JA4SResult struct {
// JA4S is the server fingerprint: ja4s_a_ja4s_b_ja4s_c.
JA4S string `json:"ja4s"`
// JA4SR is the raw (un-hashed) form a_<cipher>_<extensions in order>.
JA4SR string `json:"ja4s_r"`
A string `json:"ja4s_a"`
B string `json:"ja4s_b"`
C string `json:"ja4s_c"`
// TLSVersion is the negotiated TLS version label.
TLSVersion string `json:"tls_version"`
// Cipher is the server's single chosen cipher suite (4-hex).
Cipher string `json:"cipher"`
// ALPN is the server's chosen ALPN protocol, if any.
ALPN string `json:"alpn,omitempty"`
Note string `json:"note,omitempty"`
}
JA4SResult is the outcome of a JA4S (server) computation.
func JA4SDecode ¶ added in v0.652.0
func JA4SDecode(hexInput string) (*JA4SResult, error)
JA4SDecode accepts a ServerHello as hex (a full TLS record or a bare handshake) and returns its JA4S fingerprint.
func JA4SFromServerHello ¶ added in v0.652.0
func JA4SFromServerHello(b []byte) (*JA4SResult, error)
JA4SFromServerHello computes the JA4S (FoxIO) from raw ServerHello bytes. Unlike client JA4, JA4S hashes the extension list in ServerHello order and retains GREASE (per FoxIO's reference to_ja4s).
type Result ¶
type Result struct {
// JA3 is the fingerprint string (the pre-hash field concatenation).
JA3 string `json:"ja3"`
// JA3Digest is the MD5 of JA3, lowercase hex.
JA3Digest string `json:"ja3_digest"`
// TLSVersion is the ClientHello legacy_version as a decimal (e.g. 771 =
// 0x0303).
TLSVersion int `json:"tls_version"`
// Ciphers is the offered cipher-suite list, GREASE removed.
Ciphers []int `json:"ciphers"`
// Extensions is the extension-type list in appearance order, GREASE removed.
Extensions []int `json:"extensions"`
// Curves is the supported_groups (ext 10) list, GREASE removed.
Curves []int `json:"curves"`
// PointFormats is the ec_point_formats (ext 11) list.
PointFormats []int `json:"point_formats"`
// SNI is the server_name (ext 0), informational — not part of the JA3.
SNI string `json:"sni,omitempty"`
// Note carries interpretation guidance.
Note string `json:"note,omitempty"`
}
Result is the outcome of a Decode / FromClientHello call.
func Decode ¶
Decode accepts a ClientHello as a hex string — either a full TLS record (starting 0x16) or a bare handshake message (starting 0x01) — and returns its JA3. Whitespace and colons in the hex are ignored.
func FromClientHello ¶
FromClientHello computes the JA3 from raw ClientHello bytes. The input may be a full TLS record (record-layer header stripped) or a bare handshake message.