JA4 Algorithm
The JA4 fingerprint is constructed using the following format:
(QUIC/DTLS/TLS type)(TLS version)(SNI flag)(Number of ciphers)(Number of extensions)(ALPN abbreviation)_(Cipher hash)_(Extension hash)
Steps to Construct a JA4 Fingerprint
-
Determine Protocol Type:
- The first character of the fingerprint is:
q
for QUIC (QUIC encapsulates TLS 1.3 in UDP packets).d
for DTLS (Datagram Transport Layer Security).t
for standard TLS (Transport Layer Security over TCP).
- The first character of the fingerprint is:
-
Extract TLS Version:
- If the
supported_versions
extension (0x002b) is present, take the highest version listed in the extension. Ignore GREASE values. - If the
supported_versions
extension is not present, use the value of the Protocol Version field in the Client Hello. - Translate the version to a 2-character code:
0x0304
= 13 (TLS 1.3)0x0303
= 12 (TLS 1.2)0x0302
= 11 (TLS 1.1)0x0301
= 10 (TLS 1.0)0x0300
= s3 (SSL 3.0)0xfeff
= d1 (DTLS 1.0)0xfefd
= d2 (DTLS 1.2)0xfefc
= d3 (DTLS 1.3)- Unknown versions = 00
- If the
-
SNI Check:
- Check for the presence of the Server Name Indication (SNI) extension (0x0000).
- If SNI exists, the value is
d
(domain). - If SNI does not exist, the value is
i
(IP).
-
Count of Cipher Suites:
- Count the number of cipher suites in the Client Hello, ignoring GREASE values.
- Represent the count as a 2-character string (e.g., 06 for six cipher suites).
- If the count exceeds 99, use 99.
-
Count of Extensions:
- Count the number of extensions in the Client Hello, ignoring GREASE values.
- Represent the count as a 2-character string (e.g., 10 for ten extensions).
-
ALPN Abbreviation:
- Use the first and last characters of the first ALPN value in the ALPN extension (0x0010).
- If there is no ALPN extension or ALPN value, use 00.
-
Cipher Hash:
- Create a list of cipher suite codes from the Client Hello, sorted in hexadecimal order, ignoring GREASE values.
- Compute a SHA-256 hash of this list and take the first 12 characters.
-
Extension Hash:
- Create a list of extension codes, sorted in hexadecimal order, ignoring the SNI (0x0000) and ALPN (0x0010) extensions.
- Append the list of signature algorithms in the order they appear in the
signature_algorithms
extension. - Compute a SHA-256 hash of the combined list and take the first 12 characters.
Example JA4 Fingerprint Calculation
Step 1: Analyze the Client Hello Packet
- Protocol Type: TLS over TCP (
t
). - TLS Version: 1.3 (
13
). - SNI: Present (
d
). - Cipher Suites: 15 cipher suites (ignoring GREASE).
- Extensions: 16 extensions (ignoring GREASE).
- ALPN: The first ALPN value is
h2
, so the abbreviation ish2
. - Cipher Suite List:
1301,1302,1303,c02b,c02f,c02c,c030,cca9,cca8,c013,c014,009c,009d,002f,0035
- Extension List:
0005,000a,000b,000d,0012,0015,0017,001b,0023,002b,002d,0033,4469,ff01
- Signature Algorithms:
0403,0804,0401,0503,0805,0501,0806,0601
Step 2: Compute Hashes
Cipher Hash Calculation
- Sorted Cipher List:
002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9
- Hash (first 12 characters):
8daaf6152771
import hashlib
def compute_ja4_cipher_hash(cipher_list):
# Sort cipher list and join with commas
sorted_ciphers = ",".join(sorted(cipher_list))
# Compute SHA-256 hash and return first 12 characters
return hashlib.sha256(sorted_ciphers.encode()).hexdigest()[:12]
cipher_list = ['1301', '1302', '1303', 'c02b', 'c02f', 'c02c', 'c030', 'cca9', 'cca8', 'c013', 'c014', '009c', '009d', '002f', '0035']
cipher_hash = compute_ja4_cipher_hash(cipher_list)
print(f"Cipher Hash: {cipher_hash}") # Output: 8daaf6152771
Extension Hash Calculation
- Sorted Extension List:
0005,000a,000b,000d,0012,0015,0017,001b,0023,002b,002d,0033,4469,ff01
- Signature Algorithms Appended:
0005,000a,000b,000d,0012,0015,0017,001b,0023,002b,002d,0033,4469,ff01_0403,0804,0401,0503,0805,0501,0806,0601
- Hash (first 12 characters):
e5627efa2ab1
import hashlib
def compute_ja4_extension_hash(extension_list, signature_list):
# Sort extensions and append signatures
sorted_extensions = ",".join(sorted(extension_list))
combined_list = f"{sorted_extensions}_{','.join(signature_list)}"
# Compute SHA-256 hash and return first 12 characters
return hashlib.sha256(combined_list.encode()).hexdigest()[:12]
extension_list = ['0005', '000a', '000b', '000d', '0012', '0015', '0017', '001b', '0023', '002b', '002d', '0033', '4469', 'ff01']
signature_list = ['0403', '0804', '0401', '0503', '0805', '0501', '0806', '0601']
extension_hash = compute_ja4_extension_hash(extension_list, signature_list)
print(f"Extension Hash: {extension_hash}") # Output: e5627efa2ab1
Step 3: Assemble the JA4 Fingerprint
JA4 = t13d1516h2_8daaf6152771_e5627efa2ab1
Example Output
JA4 Fingerprint:
t13d1516h2_8daaf6152771_e5627efa2ab1
Raw Output:
The program should allow for raw outputs either sorted or original using flags:
- Sorted Raw (-r):
JA4_r = t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0015,0017,001b,0023,002b,002d,0033,4469,ff01_0403,0804,0401,0503,0805,0501,0806,0601
- Original Raw (-ro):
JA4_ro = t13d1516h2_1301,1302,1303,c02b,c02f,c02c,c030,cca9,cca8,c013,c014,009c,009d,002f,0035_001b,0000,0033,0010,4469,0017,002d,000d,0005,0023,0012,002b,ff01,000b,000a,0015_0403,0804,0401,0503,0805,0501,0806