Was ist TLS-Fingerprinting und wie funktioniert JA3?

Veröffentlicht:

18 Min. Lesezeit

Acar Diveroli
Autor: Acar Diveroli
Aus den ClientHello-Feldern im Lesetor steigt die JA3-Signatur zum Panel darüber, der Skript-Client kommt nicht durch

Sie haben ein Skript in Python geschrieben. Es geht über einen Proxy hinaus, die IP-Adresse wechselt bei jeder Anfrage, und in den User-Agent-Header haben Sie eine aktuelle Chrome-Zeile geschrieben. Trotzdem antwortet die Seite schon auf die allererste Anfrage mit 403. Öffnen Sie dieselbe Adresse über dieselbe IP in einem echten Browser, kommt die Seite. Der Unterschied steckt in einem Paket, das noch vor jedem HTTP-Header unterwegs ist: der ClientHello-Nachricht, die die verschlüsselte Verbindung eröffnet. Am Aufbau dieser Nachricht erkennt eine Seite, ob sie mit Chrome, mit Python oder mit curl spricht, ohne einen einzigen Header zu lesen.

Dieser Beitrag behandelt den TLS-Handshake, die Felder in ClientHello, wie die JA3-Zeichenkette aus diesen Feldern berechnet wird und was JA4 anders macht. Danach kommt die Frage, die uns am häufigsten erreicht: Ändert ein Proxy den TLS-Fingerabdruck? Am Ende steht ein getestetes Python-Beispiel, das Ihnen die JA3-Zeichenkette Ihres eigenen Clients lokal zeigt.

Was ist der TLS-Handshake?

TLS ist das Protokoll, das den Verkehr zwischen Browser und Seite verschlüsselt; das https:// in der Adresszeile zeigt, dass Sie es benutzen. Bevor die Verschlüsselung beginnt, müssen sich beide Seiten auf eine Version, eine Cipher Suite und einen Schlüssel einigen. Diese kurze Verhandlung heißt Handshake. Die aktuelle Version TLS 1.3 ist in RFC 8446 definiert, und der Handshake läuft grob so ab:

  1. Der Client verbindet sich per TCP mit dem Server und sendet eine ClientHello-Nachricht. Darin stehen die unterstützten Cipher Suites, die Extensions und der Schlüsselanteil.
  2. Der Server wählt aus der Liste aus und antwortet mit ServerHello. Ab hier können beide Seiten den gemeinsamen Schlüssel ableiten.
  3. Der Server sendet sein Zertifikat und eine Finished-Nachricht, die die Integrität des Handshakes belegt, beides verschlüsselt.
  4. Der Client prüft das Zertifikat und schickt seine eigene Finished-Nachricht.
  5. Erst danach geht die erste HTTP-Anfrage (GET /, Header, User-Agent) durch den verschlüsselten Kanal.

Für den Fingerabdruck zählt Schritt eins. ClientHello wird gesendet, bevor ein gemeinsamer Schlüssel existiert, ist also unverschlüsselt; der Server, das davorliegende CDN und jedes Netzgerät auf dem Weg können die Nachricht unverändert lesen, vor jedem Header, jedem Cookie und jeder Zeile JavaScript.

Welche Felder stehen in der ClientHello-Nachricht?

ClientHello ist eine Liste, in der der Client sagt: „Das kann ich sprechen." Für das Fingerprinting werden diese Felder genutzt:

FeldWas steht darin?Warum ist es unterscheidend?
legacy_versionDas alte Versionsfeld. TLS-1.3-Clients tragen hier aus Kompatibilitätsgründen weiterhin den TLS-1.2-Wert (771) einAllein wenig Aussage; die echte Version steht in einer Extension
cipher_suitesUnterstützte Cipher Suites, in der bevorzugten ReihenfolgeListe und Reihenfolge unterscheiden sich von Bibliothek zu Bibliothek
extensionsDie Typnummern der Extensions: server_name (0), supported_groups (10), signature_algorithms (13), ALPN (16), supported_versions (43), key_share (51) und weitereWelche Extensions vorhanden sind und in welcher Reihenfolge, ist softwarespezifisch
supported_groupsKurven und Gruppen für den Schlüsselaustausch (29 = x25519, 23 = secp256r1)Neue Gruppen erreichen zuerst die Browser
ec_point_formatsPunktformate elliptischer KurvenManche Bibliotheken senden drei Werte, andere nur einen
signature_algorithmsAkzeptierte Signaturalgorithmen, in ReihenfolgeJA3 nutzt es nicht, JA4 schon
ALPNDas Anwendungsprotokoll, das der Client sprechen will (h2, http/1.1)Ein Browser fragt h2 an, einfache Clients meist gar nichts

Gefüllt werden diese Listen nicht von Ihrer Anwendung, sondern von der darunterliegenden TLS-Bibliothek. Chrome nutzt BoringSSL, Firefox NSS. Pythons ssl-Modul und Node.js setzen auf OpenSSL auf. Das mit Windows gelieferte curl nutzt Schannel, den TLS-Stack des Betriebssystems. Jede Bibliothek hat eine andere Standard-Cipher-Liste, einen anderen Extension-Satz und eine andere Sortierung; so sickert die Identität des Clients in das erste Paket des Handshakes.

Was ist ein TLS-Fingerabdruck?

Ein TLS-Fingerabdruck ist die Fassung dieser ClientHello-Listen als kurze, vergleichbare Zeichenkette. Drei Eigenschaften unterscheiden ihn von anderen Signalen:

  • Er ist passiv. Die Seite lässt den Client nichts ausführen, sie liest nur das erste eintreffende Paket. JavaScript abzuschalten oder Cookies zu löschen ändert am Ergebnis nichts.
  • Er ist unabhängig von Headern. User-Agent ist eine Textzeile und lässt sich in jeder HTTP-Bibliothek mit einer Zeile ändern. ClientHello kommt aus dem kompilierten Verhalten der Bibliothek.
  • Er identifiziert die Software, nicht die Person. Alle, die dieselbe Chrome-Version auf demselben Betriebssystem fahren, erzeugen denselben Wert. Die JavaScript-Ebene (Canvas, Schriften, WebGL), die einzelne Geräte auseinanderhalten will, behandeln wir in Was ist Browser-Fingerprinting?.

Erfunden wurde die Methode nicht für Bot-Erkennung. Ihr erstes Einsatzfeld war Netzsicherheit: Schadsoftware verschlüsselt ihren Verkehr, aber TLS-Bibliothek und Einstellungen bleiben meist fest. Ein Sicherheitsteam, das den Inhalt nicht sieht, erkennt dieselbe Softwarefamilie trotzdem am Aufbau ihres ClientHello.

Was ist JA3 und wie wird es berechnet?

JA3 ist die erste verbreitete Methode, die diese Idee an ein festes Format gebunden hat. Sie wurde 2017 bei Salesforce von John Althouse, Jeff Atkinson und Josh Atkins entwickelt und als Open Source veröffentlicht. Wie im JA3-Repository von Salesforce beschrieben, hat die Berechnung fünf Schritte:

  1. Aus der ClientHello-Nachricht werden fünf Felder genommen: TLS-Version, Cipher Suites, Extensions, Kurven (supported_groups) und Punktformate elliptischer Kurven.
  2. Die Werte jedes Feldes werden in Dezimalzahlen umgewandelt und in der Reihenfolge der Nachricht mit - verkettet.
  3. Die fünf Felder werden mit , verbunden. Ist ein Feld leer, bleibt sein Platz leer.
  4. GREASE-Werte (gleich mehr dazu) kommen gar nicht erst in die Listen.
  5. Vom entstehenden String wird der MD5-Hash gebildet. Dieser 32 Zeichen lange Hash ist der JA3-Fingerabdruck.

Das Beispiel aus dem Repository sieht so aus:

text
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0
→ ada70206e40642a3e4461f35503241d5

Von links gelesen: 769 steht für TLS 1.0; dann folgen zwölf Cipher Suites, drei Extensions, drei Kurven und ein einzelnes Punktformat. MD5 dient hier nicht der Sicherheit, sondern dazu, einen langen String in einen durchsuchbaren Schlüssel fester Länge zu verwandeln.

Zwei Anmerkungen: Weil TLS-1.3-Clients den TLS-1.2-Wert ins Versionsfeld schreiben, beginnen fast alle aktuellen JA3-Strings mit 771. Salesforce hat das Repository am 1. Mai 2025 archiviert; Werkzeuge wie Wireshark berechnen den Wert weiterhin, gepflegt wird die Methode aber nicht mehr.

Was ist GREASE und warum ignoriert JA3 es?

GREASE ist ein in RFC 8701 definierter Robustheitsmechanismus. Der Client fügt seinen Cipher-Suite-, Extension- und Gruppenlisten einige zufällig gewählte reservierte Werte nach dem Muster 0x0A0A, 0x1A1A0xFAFA hinzu. Diese Werte bedeuten nichts. Ziel ist, laufend zu prüfen, ob Server unbekannte Werte stillschweigend übergehen: Ein fehlerhafter Server, der bei einem unbekannten Wert die Verbindung abbricht, fällt heute auf und nicht erst, wenn ein neues TLS-Merkmal ausgeliefert wird.

Weil die Werte zufällig sind, würde derselbe Browser bei jeder Verbindung einen anderen Hash erzeugen, wenn JA3 sie mitzählte. Deshalb verlangt die Dokumentation der Methode, GREASE-Werte zu überspringen; im Code unten erledigt das die Funktion is_grease.

Warum mischt Chrome die Reihenfolge der Extensions?

GREASE schützt den Inhalt der Listen, nicht deren Reihenfolge. Dass Server- und Middlebox-Software sich auf Chromes feste Extension-Reihenfolge verließ, barg dasselbe Risiko, also hat das Chrome-Team auch die Reihenfolge zufällig gemacht. Laut dem Eintrag in Chrome Platform Status wurde das Merkmal „TLS ClientHello extension permutation" mit Chrome 110 standardmäßig aktiviert. Die dort genannte Begründung ist nicht die Flucht vor Fingerprinting, sondern weniger Sprödigkeit im Ökosystem: Eine feste Reihenfolge verleitet Serverentwickler dazu, Chrome zu erkennen und ein bestimmtes Verhalten vorauszusetzen, was künftige Änderungen an TLS erschwert. RFC 8446 sagt ohnehin, dass Extensions in beliebiger Reihenfolge stehen dürfen; die einzige Ausnahme ist pre_shared_key, das, wenn vorhanden, zuletzt stehen muss.

Da JA3 die Extensions in der Reihenfolge der Nachricht verkettet, machte diese Änderung den JA3-Hash von Chromium-Browsern instabil. Wir haben das mit dem Listener weiter unten gemessen: 24 aufeinanderfolgende Verbindungen eines Chromium-Browsers ergaben 24 verschiedene JA3-Hashes. Sortierten wir die Extension-Nummern, war die Menge jedes Mal dieselbe; verändert hatte sich nur die Reihenfolge.

Was ist JA4 und worin unterscheidet es sich von JA3?

JA4 ist das von FoxIO veröffentlichte neuere Format, das auf dieses Problem antwortet. Laut der technischen Spezifikation zu JA4 besteht der Fingerabdruck aus drei Teilen in der Form a_b_c. Das Beispiel aus dem Dokument, t13d1516h2_8daaf6152771_e5627efa2ab1, liest sich so:

  • t: TLS über TCP (q wäre QUIC, d DTLS).
  • 13: TLS 1.3. JA4 liest die Version aus der Extension supported_versions, nicht aus dem alten Feld.
  • d: Eine SNI-Extension ist vorhanden, der Client verbindet sich also mit einem Domainnamen (i zeigt an, dass SNI fehlt).
  • 15 und 16: 15 Cipher Suites und 16 Extensions, GREASE nicht mitgezählt.
  • h2: das erste und das letzte Zeichen des ersten Werts in der ALPN-Liste, also HTTP/2.
  • 8daaf6152771: Die Hex-Codes der Cipher Suites werden sortiert, mit SHA-256 gehasht, und die ersten 12 Zeichen bleiben stehen.
  • e5627efa2ab1: Die Extension-Codes werden sortiert (ohne SNI und ALPN), die Signaturalgorithmen in ihrer ursprünglichen Reihenfolge angehängt und das Ergebnis genauso gehasht.

Das Sortieren nimmt dem Mischen der Extensions die Wirkung. Der lesbare erste Teil erlaubt eine grobe Unterscheidung, ohne auf die Hashes zu sehen: Zwei erste Teile, von denen einer auf h2 endet und der andere auf 00, weil er kein ALPN sendet, stammen nicht aus derselben Software.

JA3JA4
HerausgeberSalesforce (2017), Repository 2025 archiviertFoxIO, wird weiterentwickelt
Formatein einzelner MD5-Hash mit 32 Zeichena_b_c: lesbares Präfix + zwei gekürzte SHA-256-Hashes
Extension-ReihenfolgeReihenfolge in der Nachrichtsortiert; vom Mischen unberührt
TLS-Versionaltes Versionsfeld (771 auch bei TLS 1.3)Extension supported_versions
ALPN, SNInur als Extension-Nummer sichtbarbeide im ersten Teil ausgeschrieben
Signaturalgorithmennicht genutztgehen in den dritten Teil ein
GREASEwird ignoriertwird ignoriert

Laut dem Lizenzhinweis im Repository von FoxIO steht JA4, der TLS-Client-Fingerabdruck, unter der BSD-3-Clause-Lizenz offen; die übrigen Mitglieder der Familie (JA4S, JA4H, JA4X, JA4T und so weiter) fallen unter eine eigene FoxIO-Lizenz.

Ändert ein Proxy den TLS-Fingerabdruck?

Nein. Der Grund liegt darin, wie ein Proxy HTTPS-Verkehr transportiert.

Beim HTTP-Proxy schickt der Client dem Proxy zuerst eine Anfrage CONNECT ziel.com:443. Der Proxy öffnet eine TCP-Verbindung zum Ziel, antwortet 200 Connection Established und reicht von da an nur noch Bytes weiter. ClientHello läuft innerhalb dieses Tunnels: Ihr Client erzeugt es, die Zielseite liest es, und der Proxy ändert weder den Inhalt noch schreibt er ihn neu. Bei SOCKS5 ist es genauso; das Protokoll reicht die TCP-Verbindung eine Schicht tiefer durch und weiß nicht einmal, dass die transportierten Daten TLS sind. Den Ablauf beider Protokolle haben wir Schritt für Schritt in SOCKS und HTTP Proxy im Vergleich beschrieben.

Das Ergebnis: Die Seite sieht gleichzeitig die IP-Adresse des Proxys und den TLS-Fingerabdruck Ihres Clients. Daran ändert weder HTTPS-Proxy noch SOCKS5-Proxy etwas, und auch nicht, ob der Proxy Residential, Mobile oder Datacenter ist. Wir haben es mit einem lokalen Test-Proxy ausprobiert: Derselbe curl-Client verband sich direkt, über einen HTTP-CONNECT-Tunnel und über SOCKS5 mit dem Listener unten, und der Listener notierte alle drei Male dieselbe JA3-Zeichenkette.

Der Fingerabdruck ändert sich nur in einem Fall: wenn dazwischen eine Instanz sitzt, die TLS terminiert:

Was dazwischensitztWer baut TLS zum Ziel auf?Welchen Fingerabdruck sieht die Seite?
HTTP-Proxy (CONNECT-Tunnel)Ihr Clientden Ihres Clients
SOCKS5-ProxyIhr Clientden Ihres Clients
VPNIhr Clientden Ihres Clients
Firmen-Gateway mit TLS-Inspektiondas Gatewayden des Gateways
Virenscanner mit aktiver HTTPS-Prüfungder Virenscannerden des Virenscanners
Dienst, der die Seite für Sie abruftder Client des Dienstesden des Dienst-Clients

Die fünfte Zeile haben wir beim Vorbereiten dieses Beitrags auf der eigenen Maschine erlebt: curl unter Windows lieferte einen Fingerabdruck, wenn es sich direkt mit dem Echo-Dienst verband, und einen anderen über den lokalen Proxy-Tunnel. Die Ursache war die HTTPS-Prüfung des Virenscanners, die die direkte Verbindung mit ihrem eigenen TLS-Stack neu aufbaute. Sieht der Wert beim Echo-Dienst nicht nach der erwarteten Bibliothek aus, prüfen Sie, wer das Zertifikat signiert hat.

Wie sehen Sie Ihre eigene JA3-Zeichenkette?

Der kürzeste Weg ist ein Echo-Dienst: Öffnen Sie die TLS-Seite von BrowserLeaks im Browser, und Sie sehen Ihre JA3- und JA4-Werte. Wollen Sie auf Paketebene schauen, berechnet Wireshark diese Werte selbst an ClientHello-Paketen; in der Referenz der Anzeigefilter stehen die Felder als tls.handshake.ja3 und tls.handshake.ja4.

Um die Berechnung selbst zu sehen, können Sie das Skript unten nutzen. Es läuft allein mit Pythons Standardbibliothek und sendet nichts nach außen: Es öffnet auf Port 8443 einen rohen TCP-Listener, zerlegt die ClientHello-Nachricht jedes verbundenen Clients und gibt die JA3-Zeichenkette samt Hash aus. Beim Start verbindet es einmal Pythons eigenen urllib-Client. Der Listener schließt den Handshake nicht ab, er liest das erste Paket und trennt; ein Verbindungsfehler auf Client-Seite ist zu erwarten.

python
import hashlib
import socket
import threading
import urllib.request

HOST, PORT = "127.0.0.1", 8443


def is_grease(value):
    # RFC 8701: 0x0a0a, 0x1a1a, ... 0xfafa
    return (value & 0x0F0F) == 0x0A0A and (value >> 8) == (value & 0xFF)


def read_client_hello(conn):
    data = b""
    while len(data) < 5:
        data += conn.recv(4096)
    if data[0] != 22:  # 22 = Handshake-Record
        raise ValueError("kein TLS-Handshake")
    record_length = int.from_bytes(data[3:5], "big")
    while len(data) < 5 + record_length:
        chunk = conn.recv(4096)
        if not chunk:
            break
        data += chunk
    return data[5 : 5 + record_length]


def ja3_from_client_hello(hello):
    if hello[0] != 1:  # 1 = ClientHello
        raise ValueError("kein ClientHello")
    pos = 4  # Nachrichtentyp (1) + Länge (3)
    version = int.from_bytes(hello[pos : pos + 2], "big")
    pos += 2 + 32  # Version + random
    pos += 1 + hello[pos]  # session_id
    size = int.from_bytes(hello[pos : pos + 2], "big")
    pos += 2
    ciphers = [int.from_bytes(hello[i : i + 2], "big") for i in range(pos, pos + size, 2)]
    pos += size
    pos += 1 + hello[pos]  # compression_methods
    end = pos + 2 + int.from_bytes(hello[pos : pos + 2], "big")
    pos += 2

    extensions, groups, point_formats = [], [], []
    while pos < end:
        ext_type = int.from_bytes(hello[pos : pos + 2], "big")
        ext_size = int.from_bytes(hello[pos + 2 : pos + 4], "big")
        body = hello[pos + 4 : pos + 4 + ext_size]
        pos += 4 + ext_size
        extensions.append(ext_type)
        if ext_type == 10:  # supported_groups
            groups = [int.from_bytes(body[i : i + 2], "big") for i in range(2, len(body), 2)]
        elif ext_type == 11:  # ec_point_formats
            point_formats = list(body[1:])

    def join(values):
        return "-".join(str(v) for v in values if not is_grease(v))

    fields = [str(version), join(ciphers), join(extensions), join(groups), join(point_formats)]
    ja3_text = ",".join(fields)
    return ja3_text, hashlib.md5(ja3_text.encode()).hexdigest()


def own_python_client():
    try:
        urllib.request.urlopen(f"https://localhost:{PORT}", timeout=5)
    except OSError:
        pass  # der Listener trennt ohne Antwort, das ist erwartet


def main():
    server = socket.create_server((HOST, PORT))
    print(f"lauscht auf https://localhost:{PORT}, Ctrl+C zum Beenden")
    threading.Thread(target=own_python_client, daemon=True).start()
    while True:
        conn, _ = server.accept()
        with conn:
            try:
                ja3_text, ja3_hash = ja3_from_client_hello(read_client_hello(conn))
            except (ValueError, IndexError, OSError) as exc:
                print("nicht lesbar:", exc)
                continue
        print(ja3_hash, ja3_text)


if __name__ == "__main__":
    main()

Richten Sie während der Laufzeit des Skripts aus einem anderen Terminal verschiedene Clients auf dieselbe Adresse. Schreiben Sie localhost in die Adresse; ein Client, der sich mit einer IP-Adresse verbindet, sendet keine SNI-Extension, und der Fingerabdruck ändert sich.

bash
curl -k https://localhost:8443
node -e "require('https').get('https://localhost:8443', { rejectUnauthorized: false }).on('error', () => {})"

Auf unserer Maschine (Windows 11, Python 3.13 mit OpenSSL 3.0, Node.js 24, curl 8 mit Schannel gebaut) sah die Ausgabe so aus. Das Cipher-Suite-Feld haben wir zur besseren Lesbarkeit gekürzt:

text
331a436afb23d4e31134c11b301bdcb5 771,4866-4867-4865-…,0-11-10-35-16-22-23-49-13-43-45-51-21,29-23-30-25-24-256-257-258-259-260,0-1-2
2e6c64f66822fc35b6a7a128b557f1de 771,4866-4865-49196-…,0-43-13-35-10-11-16-51-49-23-65281-45,29-23-24,0
944d1e1858cd278718f8a46b65d3212f 771,4866-4867-4865-…,65281-0-11-10-35-22-23-13-43-45-51,4588-29-23-30-24-25-256-257,0-1-2

Dieselbe Maschine, dieselbe IP, drei getrennte Identitäten: der Reihe nach Python, curl und Node.js. Für Python gab der Echo-Dienst genau denselben Hash zurück, der Parser arbeitet also korrekt. Die 4588 in der Node.js-Zeile ist die im IANA-Register als X25519MLKEM768 geführte hybride Post-Quanten-Gruppe für den Schlüsselaustausch; diese Python-Version sendet sie nicht. Im selben Node.js lieferten https.get und das eingebaute fetch ebenfalls verschiedene JA3-Werte, weil fetch die ALPN-Extension ergänzt: Der Fingerabdruck hängt an der verwendeten HTTP-Bibliothek, nicht an der Sprache. Die Unterschiede auf der Python-Seite haben wir in HTTPX, Requests und AIOHTTP im Vergleich gegenübergestellt.

Für die Wirkung eines Proxys taugt der Listener nicht (ein entfernter Proxy erreicht Ihr localhost nicht), fragen Sie deshalb den Echo-Dienst zweimal:

python
import json
import urllib.request

PROXY = "http://user:pass@pr.proxynet.io:8000"
URL = "https://tls.browserleaks.com/json"


def fingerprint(opener):
    with opener.open(URL, timeout=15) as response:
        result = json.load(response)
    return result["ja3_hash"], result["ja4"]


direct = urllib.request.build_opener(urllib.request.ProxyHandler({}))
proxied = urllib.request.build_opener(urllib.request.ProxyHandler({"https": PROXY}))

print("direkt :", *fingerprint(direct))
print("Proxy  :", *fingerprint(proxied))

In beiden Zeilen sehen Sie dieselben Werte; geändert hat sich nur die IP-Adresse, die der Dienst sieht.

Wie nutzt ein Seitenbetreiber diese Daten?

CDN- und Firewall-Produkte stellen den JA3- oder JA4-Wert als Feld neben jeder Anfrage bereit. Der Seitenbetreiber verwendet dieses Feld beim Schreiben von Regeln und beim Durchsehen der Logs. Typische Verwendungen:

  • Konsistenzprüfung. Sagt User-Agent „Chrome", passt der Fingerabdruck aber zu keiner bekannten Chrome-Version, wurde der Header verändert. Allein ist das kein Beweis, aber ein starkes Signal, das in die Bewertung eingeht. Den Header selbst behandeln wir in Was ist ein User-Agent?.
  • Ratenbegrenzung unabhängig von der IP. Trägt Verkehr über Hunderte IPs denselben Fingerabdruck, kann der Zähler statt an die IP an den Fingerabdruck gebunden werden. Eine rotierende IP setzt diesen Zähler nicht zurück.
  • Bekannte Werkzeuge erkennen. Für Schadsoftware-Familien und Scan-Werkzeuge gibt es zusammengetragene Fingerabdrucklisten; Sicherheitsteams gleichen sie mit ihren Logs ab.

Es gibt auch Grenzen. Eine Seite, die den Wert eines aktuellen Browsers sperrt, sperrt alle Besucher, die diesen Browser nutzen. Browser-Updates verändern den Wert, und Firmen-Gateways wie Virenscanner schieben ihre eigenen Fingerabdrücke dazwischen. Deshalb entscheidet ein TLS-Fingerabdruck nichts allein; er ist eines der Signale, die zusammen mit IP-Reputation und Verhalten in die Bewertung eingehen. Diese Bewertung als Ganzes behandeln wir in Wie funktioniert Bot-Erkennung?, die Schichten von Cloudflare in Cloudflare Precursor.

Was bedeutet das für Entwickler, die Daten sammeln?

Zurück zur Lage aus der Einleitung. Der Python-Client sagt in seiner User-Agent-Zeile, er sei Chrome, sein ClientHello sagt, er sei OpenSSL. Die Seite sieht den Widerspruch im ersten Paket. Die richtige Reaktion ist, den Widerspruch aufzulösen, nicht ihn zu verbergen:

  • Behaupten Sie nicht, ein Browser zu sein, der Sie nicht sind. Der User-Agent Ihres Skripts soll das Skript vorstellen: ein Name, eine Version und eine Kontaktadresse. Einen Client, der mit ehrlicher Identität kommt, sich an robots.txt hält und langsam arbeitet, kann ein Seitenbetreiber erkennen und auf eine Freigabeliste setzen. Die Regeln erklären wir in Was ist robots.txt und wie liest man die Datei?.
  • Gibt es eine offizielle API, nutzen Sie sie. Eine Anfrage mit API-Schlüssel hat ohnehin eine bekannte Identität; die Fingerabdruck-Frage erledigt sich.
  • Braucht die Seite wirklich einen Browser, nehmen Sie einen echten. Öffnen Sie eine per JavaScript gezeichnete Seite mit Playwright in einem echten Chromium, ist Ihr Client tatsächlich dieser Browser; Header, TLS-Schicht und JavaScript-Umgebung passen zueinander. Die Einrichtung steht in Was ist Playwright und wie nutzt man es mit Proxy?.
  • Drosseln Sie das Tempo und fragen Sie um Erlaubnis. Für regelmäßige, umfangreiche Arbeit ist eine Nachricht an den Seitenbetreiber oft die haltbarste Lösung.

Die übrigen Gründe für Sperren und die legitimen Wege dorthin haben wir in Web Scraping ohne Sperren gesammelt.

Anwendungsfälle

  • 403 im Scraper diagnostizieren: Kommt ein Browser mit derselben IP durch und das Skript nicht, liegt der Unterschied höchstwahrscheinlich in der Client-Identität. Die Statuscodes trennen wir in HTTP-Statuscodes beim Web Scraping, den Gesamtaufbau zeigt unsere Seite zur Daten-Scraping-Lösung.
  • Bot-Verkehr auf der eigenen Seite trennen: Gruppieren Sie Logs nach Fingerabdruck, zeigt sich der verteilte Verkehr, den eine Gruppierung nach IP übersieht.
  • Den eigenen Crawler vorstellen: Eine feste Bibliotheksversion erzeugt einen gleichbleibenden Fingerabdruck, und genau so wird Ihr Crawler in den Logs wiedererkannt. Die Infrastrukturseite steht auf unserer Seite zur Web-Crawler-Lösung.
  • Die Erwartung an einen Proxy richtig setzen: Residential-Proxy ändern IP-Reputation und Standort; die Identität Ihres Clients bleibt Ihre Verantwortung.

Häufige Fehler

  • Glauben, mit dem User-Agent ändere sich der Client. Der Header ist Text; ClientHello ist das Verhalten der Bibliothek und geht vor dem Header raus.
  • Vom Proxy erwarten, dass er den TLS-Fingerabdruck ändert. Ein tunnelnder Proxy ändert die IP und fasst den Handshake nicht an.
  • Erwarten, dass der JA3-Hash in einem Chromium-Browser stabil bleibt. Die Extension-Reihenfolge wechselt bei jeder Verbindung; für Vergleiche eignen sich JA4 oder eine sortierte Extension-Liste.
  • Das Ergebnis des Echo-Dienstes ungeprüft hinnehmen. Ein Virenscanner oder Firmen-Gateway mit HTTPS-Prüfung zeigt dem Dienst seinen eigenen Fingerabdruck.
  • Beim Messen 127.0.0.1 verwenden. Es wird kein SNI gesendet, und der Wert weicht von dem echter Verbindungen ab.

Entscheidungshilfe

LageEmpfehlung
Die IP wechselt über den Proxy, trotzdem kommt bei der ersten Anfrage 403Sehen Sie sich die Client-Identität an: Sagen User-Agent und die genutzte Bibliothek dasselbe?
Sie wollen den JA3- oder JA4-Wert Ihres eigenen Clients kennenEin Echo-Dienst oder der lokale Listener oben
Sie wollen Chrome-Verkehr in Ihren Logs gruppierenJA4 statt JA3; vom Mischen der Extensions unberührt
Die Seite braucht JavaScript und einen echten BrowserEchte Browser-Automatisierung (Playwright), vernünftiges Tempo, der von der Seite erlaubte Umfang
Der Echo-Dienst zeigt einen unerwarteten WertPrüfen Sie, wer das Zertifikat signiert hat; dazwischen kann Software TLS terminieren
Auf Ihrer Seite läuft verteilter Bot-VerkehrBinden Sie die Ratenbegrenzung an das Paar IP + Fingerabdruck statt an die IP; sperren Sie nicht auf einen einzelnen Hash

Häufige Fragen

Worin unterscheiden sich JA3 und JA3S?

JA3 entsteht aus der ClientHello-Nachricht des Clients, JA3S aus der ServerHello-Nachricht des Servers. Weil ein Server verschiedenen Clients verschieden antwortet, identifiziert JA3S den Server nicht allein; seine Antwort an denselben Client ist dagegen immer dieselbe. Deshalb nutzen Sicherheitsteams beide als Paar.

Ändert ein VPN den TLS-Fingerabdruck?

Nein. Ein VPN schickt den Verkehr durch einen verschlüsselten Tunnel, den TLS-Handshake mit der Seite führt aber weiterhin Ihr Browser oder Ihr Skript. Die Seite sieht die IP-Adresse des VPN-Servers und den Fingerabdruck Ihres Clients. Den Unterschied der beiden Werkzeuge erklären wir in Proxy und VPN im Vergleich.

Identifiziert mich ein TLS-Fingerabdruck persönlich?

Allein nicht. Alle, die dieselbe Browser-Version auf demselben Betriebssystem fahren, erzeugen denselben Wert. Eine Identität, die einer Person nahekommt, entsteht erst, wenn dieser Wert mit IP-Adresse, Cookies und Signalen der JavaScript-Ebene kombiniert wird.

Ändert ein Browser-Update den Fingerabdruck?

Das kann sein. Entfernt eine neue Version eine Cipher Suite oder ergänzt eine Extension, ändern sich Liste und Hash. Deshalb werden Fingerabdrucklisten Version für Version gepflegt.

Wirken sich ein privates Fenster oder gelöschte Cookies auf den TLS-Fingerabdruck aus?

Nein. Die ClientHello-Nachricht erzeugt die TLS-Bibliothek des Browsers; Verlauf, Cookies und Fenstertyp gehen nicht in diese Listen ein.

Was tue ich, wenn ich wegen meines Fingerabdrucks gesperrt werde?

Messen Sie zuerst: Sehen Sie nach, welcher Wert beim Echo-Dienst erscheint und ob dazwischen Software TLS terminiert. Schreiben Sie ein Skript, nennen Sie Ihre Identität ehrlich, drosseln Sie das Tempo und nutzen Sie die offizielle API oder den Freigabekanal der Seite. Werden Sie mit einem gewöhnlichen Browser gesperrt, liegt das Problem höchstwahrscheinlich an der IP-Reputation, nicht am Fingerabdruck.

Fazit

Ein TLS-Fingerabdruck stammt aus ClientHello, dem ersten und unverschlüsselten Paket einer verschlüsselten Verbindung. JA3 verkettet die fünf Listen dieser Nachricht der Reihe nach und bildet den MD5-Hash; als Chrome begann, die Extension-Reihenfolge zu mischen, wurde der Hash instabil, und JA4 hat das durch Sortieren behoben. Der Wert zeigt die Software, nicht die Person, geht vor den Headern raus, und kein tunnelnder Proxy fasst ihn an: Der Proxy ändert die IP, die Identität des Clients bleibt bei Ihnen. Um einer gleichbleibenden und ehrlichen Client-Identität den passenden IP-Typ zur Seite zu stellen, sehen Sie sich unsere Proxy-Dienste an.

ChatGPT fragenClaude fragen