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:
- 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. - Der Server wählt aus der Liste aus und antwortet mit
ServerHello. Ab hier können beide Seiten den gemeinsamen Schlüssel ableiten. - Der Server sendet sein Zertifikat und eine
Finished-Nachricht, die die Integrität des Handshakes belegt, beides verschlüsselt. - Der Client prüft das Zertifikat und schickt seine eigene
Finished-Nachricht. - 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:
| Feld | Was steht darin? | Warum ist es unterscheidend? |
|---|---|---|
legacy_version | Das alte Versionsfeld. TLS-1.3-Clients tragen hier aus Kompatibilitätsgründen weiterhin den TLS-1.2-Wert (771) ein | Allein wenig Aussage; die echte Version steht in einer Extension |
cipher_suites | Unterstützte Cipher Suites, in der bevorzugten Reihenfolge | Liste und Reihenfolge unterscheiden sich von Bibliothek zu Bibliothek |
extensions | Die Typnummern der Extensions: server_name (0), supported_groups (10), signature_algorithms (13), ALPN (16), supported_versions (43), key_share (51) und weitere | Welche Extensions vorhanden sind und in welcher Reihenfolge, ist softwarespezifisch |
supported_groups | Kurven und Gruppen für den Schlüsselaustausch (29 = x25519, 23 = secp256r1) | Neue Gruppen erreichen zuerst die Browser |
ec_point_formats | Punktformate elliptischer Kurven | Manche Bibliotheken senden drei Werte, andere nur einen |
signature_algorithms | Akzeptierte Signaturalgorithmen, in Reihenfolge | JA3 nutzt es nicht, JA4 schon |
| ALPN | Das 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-Agentist eine Textzeile und lässt sich in jeder HTTP-Bibliothek mit einer Zeile ändern.ClientHellokommt 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:
- Aus der
ClientHello-Nachricht werden fünf Felder genommen: TLS-Version, Cipher Suites, Extensions, Kurven (supported_groups) und Punktformate elliptischer Kurven. - Die Werte jedes Feldes werden in Dezimalzahlen umgewandelt und in der Reihenfolge der Nachricht mit
-verkettet. - Die fünf Felder werden mit
,verbunden. Ist ein Feld leer, bleibt sein Platz leer. - GREASE-Werte (gleich mehr dazu) kommen gar nicht erst in die Listen.
- 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:
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0
→ ada70206e40642a3e4461f35503241d5Von 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, 0x1A1A … 0xFAFA 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 (qwäre QUIC,dDTLS).13: TLS 1.3. JA4 liest die Version aus der Extensionsupported_versions, nicht aus dem alten Feld.d: Eine SNI-Extension ist vorhanden, der Client verbindet sich also mit einem Domainnamen (izeigt an, dass SNI fehlt).15und16: 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.
| JA3 | JA4 | |
|---|---|---|
| Herausgeber | Salesforce (2017), Repository 2025 archiviert | FoxIO, wird weiterentwickelt |
| Format | ein einzelner MD5-Hash mit 32 Zeichen | a_b_c: lesbares Präfix + zwei gekürzte SHA-256-Hashes |
| Extension-Reihenfolge | Reihenfolge in der Nachricht | sortiert; vom Mischen unberührt |
| TLS-Version | altes Versionsfeld (771 auch bei TLS 1.3) | Extension supported_versions |
| ALPN, SNI | nur als Extension-Nummer sichtbar | beide im ersten Teil ausgeschrieben |
| Signaturalgorithmen | nicht genutzt | gehen in den dritten Teil ein |
| GREASE | wird ignoriert | wird 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 dazwischensitzt | Wer baut TLS zum Ziel auf? | Welchen Fingerabdruck sieht die Seite? |
|---|---|---|
HTTP-Proxy (CONNECT-Tunnel) | Ihr Client | den Ihres Clients |
| SOCKS5-Proxy | Ihr Client | den Ihres Clients |
| VPN | Ihr Client | den Ihres Clients |
| Firmen-Gateway mit TLS-Inspektion | das Gateway | den des Gateways |
| Virenscanner mit aktiver HTTPS-Prüfung | der Virenscanner | den des Virenscanners |
| Dienst, der die Seite für Sie abruft | der Client des Dienstes | den 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.
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.
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:
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-2Dieselbe 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:
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-AgentIhres Skripts soll das Skript vorstellen: ein Name, eine Version und eine Kontaktadresse. Einen Client, der mit ehrlicher Identität kommt, sich anrobots.txthä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
403im 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;ClientHelloist 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.1verwenden. Es wird kein SNI gesendet, und der Wert weicht von dem echter Verbindungen ab.
Entscheidungshilfe
| Lage | Empfehlung |
|---|---|
Die IP wechselt über den Proxy, trotzdem kommt bei der ersten Anfrage 403 | Sehen 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 kennen | Ein Echo-Dienst oder der lokale Listener oben |
| Sie wollen Chrome-Verkehr in Ihren Logs gruppieren | JA4 statt JA3; vom Mischen der Extensions unberührt |
| Die Seite braucht JavaScript und einen echten Browser | Echte Browser-Automatisierung (Playwright), vernünftiges Tempo, der von der Seite erlaubte Umfang |
| Der Echo-Dienst zeigt einen unerwarteten Wert | Prüfen Sie, wer das Zertifikat signiert hat; dazwischen kann Software TLS terminieren |
| Auf Ihrer Seite läuft verteilter Bot-Verkehr | Binden 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.




