TLS Fingerprinting and JA3: How It Works and What Researchers Do About It

TLS fingerprinting is a network-level technique that identifies the software or device making an HTTPS connection by analyzing the parameters of its TLS ClientHello message — the first packet sent when a client initiates a secure connection — without decrypting any traffic.

Because different TLS implementations (OpenSSL, BoringSSL, NSS, WolfSSL) produce distinct ClientHello structures, a server can determine with high confidence whether a connection came from Chrome on Windows, Python’s requests library, a headless browser, or a mobile Android device — even before a single HTTP header is read.

This page explains how TLS fingerprinting works, how the widely used JA3 algorithm turns ClientHello fields into a hash, how bot-detection services apply it, and how security researchers and authorized testing tools like Damru approach the challenge.


How TLS Works (Brief Context)

When your browser visits https://example.com, the very first message it sends is a ClientHello. This message announces:

The server responds with a ServerHello selecting the agreed parameters, and the handshake proceeds. None of this requires decrypting the connection — the ClientHello is plaintext metadata.


What Is JA3?

JA3 is a method developed by Salesforce security researchers (John Althouse, Jeff Atkinson, Josh Atkins) in 2017 that distills the TLS ClientHello into a compact 32-character MD5 hash. The hash encodes five fields:

  1. TLS version (e.g., 771 for TLS 1.2)
  2. Cipher suites (comma-separated decimal values in order)
  3. Extension types (comma-separated decimal values)
  4. Elliptic curves (named groups / supported groups)
  5. Elliptic curve point formats

These fields are concatenated with - delimiters and MD5-hashed. Example:

SSLVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
771,4865-4866-4867-...,0-23-65281-...,29-23-24,...,0
→ MD5 → 773906b0efdefa24a7f2b8eb6985bf42

Each major browser and HTTP library produces a characteristic JA3 hash. Chrome 120 desktop, Chrome for Android 120, Firefox, curl, and the Python requests library all produce different hashes because their underlying TLS implementations differ.

JA3S: The Server-Side Variant

JA3S fingerprints the ServerHello in the same way, enabling passive fingerprinting of server software without active probing.


How Bot-Detection Services Use TLS Fingerprinting

Detection platforms deploy TLS fingerprinting as a pre-HTTP signal — they see the ClientHello before the HTTP request is parsed, before cookies are read, and before any JavaScript runs. This makes it one of the earliest and most reliable filtering layers.

A detection pipeline typically works as follows:

Incoming TCP connection
    └─ ClientHello captured at load balancer / reverse proxy
         └─ JA3 hash computed
              ├─ Known good hash (real Chrome Android)? → Pass through
              ├─ Known bot hash (Python requests, Scrapy, curl)? → Block / CAPTCHA
              └─ Unknown hash? → Escalate to JS challenge

Why Headless Browsers Fail This Check

Desktop Chrome uses Google’s BoringSSL fork. Headless Chrome, run via Playwright or Selenium on a Linux server, uses the same BoringSSL but may differ from a user-installed Chrome in:

These differences create JA3 hashes that, while resembling Chrome, do not exactly match the distribution of hashes seen in real user traffic — a statistical anomaly that ML-based detectors exploit.

JA3 Is Not Foolproof

JA3 alone is a weak signal because hashes can collide: multiple different clients can produce the same hash. Modern detection stacks combine JA3 with:


TLS Fingerprint Randomization: Research Approaches

For authorized testing and fingerprinting research, several approaches exist to alter or randomize a client’s TLS fingerprint:

Approach 1: TLS Interception Proxies

A local proxy (e.g., a custom mitmproxy plugin) intercepts the outgoing ClientHello and rewrites it before forwarding to the server. This requires the proxy to have a signing certificate trusted by the client.

Limitation: The proxy itself may introduce latency artifacts or TLS anomalies detectable at the server.

Approach 2: BoringSSL Patch / Custom Build

Compile a patched version of Chrome or Chromium where the cipher suite list, extension order, or GREASE values are altered. This is the approach used by tools like tlsfingerprint.io research builds.

Limitation: Time-consuming to maintain across Chrome version updates.

Approach 3: Using a Real Android Browser (Damru)

The most authentic approach is to use a client whose TLS stack genuinely differs from desktop Chrome. Chrome for Android uses BoringSSL compiled against Android’s NDK with platform-specific settings, producing JA3 hashes that are distinct from desktop Chrome and consistent with real Android traffic.

Damru provides this by running Chrome for Android inside a Redroid container:

from damru import DamruSession
import asyncio

async def capture_tls_fingerprint():
    """
    Open a session and log the JA3 hash observed by a test endpoint.
    Useful for verifying that the Android TLS stack is in use.
    """
    async with DamruSession(
        device_profile="pixel_8",
        randomize_tls=False,   # Use authentic Android BoringSSL, no randomization
    ) as session:
        page = await session.new_page()
        # tls.peet.ws reflects your JA3 hash back to you
        await page.goto("https://tls.peet.ws/api/all")
        raw = await page.inner_text("pre")
        print(raw)  # Inspect ja3, ja3n, akamai fields

asyncio.run(capture_tls_fingerprint())

Damru also supports randomize_tls=True, which shuffles non-critical extension order and GREASE values within Android-valid ranges — useful for studying how detection models respond to fingerprint variance in research environments.

When you need many distinct Android TLS stacks at once, the Damru instance manager orchestrates a pool of workers and lets you watch a live capture from any one of them.


JA3 vs. Newer Fingerprinting Methods

MethodWhat It CapturesDetection Layer
JA3TLS ClientHello hashNetwork (pre-HTTP)
JA3NJA3 without GREASE valuesNetwork
JA4Structured TLS + HTTP/2 + QUIC + SSHNetwork (multi-protocol)
Akamai HTTP/2HTTP/2 SETTINGS frames, stream priorityTransport
Canvas / WebGLBrowser rendering fingerprintsJavaScript (in-page)
BehavioralMouse, scroll, keystroke timingJavaScript (in-page)

JA4 (released 2023) addresses several JA3 limitations — it is version-aware, handles TLS 1.3 more precisely, and extends to HTTP/2, QUIC, and SSH. Expect JA4 to become the dominant network fingerprinting standard through 2026–2027.


FAQ

What is TLS fingerprinting? TLS fingerprinting is the practice of identifying a client’s software or device by analyzing the structure of its TLS ClientHello message — the opening packet of an HTTPS connection — without decrypting any traffic. Each TLS implementation (browser, library, OS) produces a characteristic ClientHello pattern that can be hashed into a compact identifier.

What is a JA3 hash? A JA3 hash is a 32-character MD5 fingerprint of five fields from a TLS ClientHello message: TLS version, cipher suites, extension types, elliptic curves, and elliptic curve point formats. It was developed by Salesforce researchers in 2017 and is widely used in network intrusion detection, bot filtering, and traffic analysis.

Can you bypass TLS fingerprinting? In authorized research and testing contexts, TLS fingerprints can be altered by using a genuine client with a different TLS stack (such as an Android browser via Damru), patching a browser’s TLS library build, or routing traffic through an intercepting proxy that rewrites the ClientHello. Modern detection stacks layer TLS fingerprinting with HTTP/2, behavioral, and canvas signals, so a single-layer bypass is rarely sufficient.

How does Damru randomize TLS fingerprints? Damru runs Chrome for Android inside a Redroid container, which uses Android’s BoringSSL build natively — producing a genuine Android JA3 hash without any patching. When randomize_tls=True is set, Damru additionally shuffles non-semantic extension ordering and GREASE values within ranges observed in real Android traffic, enabling controlled fingerprint variance for detection-evasion research in authorized test environments.