# ja4
[](https://crates.io/crates/ja4)
[](https://docs.rs/ja4)
[](#license)
[](#no-std-support)
[](#security)
A high-performance, production-ready, `#![no_std]` Rust implementation of the **JA4 TLS Client Fingerprinting** algorithm.
JA4 is a modular, human-readable TLS client fingerprinting standard designed to replace the legacy JA3 method. It normalizes TLS GREASE values, sorts ciphers and extensions, and formats the output into three structured, searchable segments (`a_b_c`) representing:
- **Part a**: Connection characteristics (protocol, version, SNI, ciphers/extensions counts, first ALPN chars).
- **Part b**: A truncated hash of the sorted list of cipher suites.
- **Part c**: A truncated hash of the sorted list of extensions and signature algorithms.
## Features
- **Zero-Allocation**: No heap allocations (`std::vec`, `std::string`, etc.) on the parser's critical path.
- **Pure Rust**: 100% Rust implementation utilizing the pure-Rust `sha2` crate.
- **Unsafe-Free**: Enforces `#![forbid(unsafe_code)]` for high-assurance environments.
- **`no_std` Support**: Compiles out-of-the-box in bare-metal, embedded, and kernel-level (e.g., eBPF/Wasm) environments.
- **Robust & Safe**: Extensively bounds-checked and panic-free parser.
## Installation
Add the following to your `Cargo.toml`:
```toml
[dependencies]
ja4 = "0.1"
```
## Usage Example
### TCP (with TLS Record Header)
```rust
use ja4::parse_ja4;
fn main() {
// A raw TLS ClientHello packet captured from a TCP stream
let packet: &[u8] = &[
0x16, 0x03, 0x01, 0x00, 0x2f, // TLS Record Header
0x01, 0x00, 0x00, 0x2b, // Handshake Header (ClientHello)
0x03, 0x03, // Version TLS 1.2
// ... (remaining payload / random / ciphers / extensions)
];
match parse_ja4(packet, false) {
Some(result) => {
println!("JA4 Fingerprint: {}", result.as_str());
// Outputs: t12i010000_8daaf6152771_000000000000 (example)
}
None => {
eprintln!("Failed to parse ClientHello packet.");
}
}
}
```
### QUIC (No Record Header)
When analyzing QUIC-based TLS (e.g., HTTP/3), the TLS payload does not start with a 5-byte TCP TLS record header. Pass `true` for the `is_quic` parameter:
```rust
use ja4::parse_ja4;
fn parse_quic_payload(quic_tls_payload: &[u8]) {
if let Some(result) = parse_ja4(quic_tls_payload, true) {
println!("QUIC JA4 Fingerprint: {}", result);
}
}
```
## Anatomy of a JA4 Fingerprint
A JA4 fingerprint consists of three parts separated by underscores (e.g., `t13d1516h2_8daaf6152771_e5627efa2ab1`):
### Part A: Connection Details (`t13d1516h2`)
- **Protocol** (`t` or `q`): `t` for TCP, `q` for QUIC.
- **Version** (`13`, `12`, etc.): The highest TLS version supported by the client.
- **SNI** (`d` or `i`): `d` if SNI (Server Name Indication) is present, `i` if not.
- **Ciphers Count** (2 digits): Number of cipher suites offered (capped at `99`).
- **Extensions Count** (2 digits): Number of TLS extensions offered (capped at `99`).
- **ALPN** (2 chars): First and last alphanumeric characters of the first ALPN protocol (e.g., `h2`), or `00` if not present.
### Part B: Ciphers Hash (`8daaf6152771`)
- The first 12 hex characters of the SHA-256 hash of the sorted list of cipher suites.
### Part C: Extensions & Signature Algorithms Hash (`e5627efa2ab1`)
- The first 12 hex characters of the SHA-256 hash of the sorted list of extensions, followed by the sorted signature algorithms list.
## Security & Safety
This crate is designed for use in critical traffic inspection infrastructures.
- **No Unsafe Code**: Enforced by `#![forbid(unsafe_code)]`.
- **Panic Protection**: Every index, slice, and offset lookup uses safe bounds-checking methods, avoiding out-of-bounds panics even on highly corrupted or malicious packets.
## License
Licensed under either of:
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.