AntiDetect
Funktionen Technologie Preise Für wen
Doks Blog FAQ
Sprache
EnglishItalianoDeutschEspañolРусский
Download

Canvas-Rauschen ist selbst ein Fingerprint

Den Canvas-Hash zu randomisieren wirkt wie die naheliegende Abwehr. Es ist zugleich der zuverlässigste Weg, anzukündigen, dass du einen Antidetect-Browser benutzt — und der Grund ist Arithmetik, nicht Kryptografie.

Die Abwehr, die sich umkehrt

Canvas-Fingerprinting funktioniert, weil GPU-Rasterisierung auf Pixelebene nicht standardisiert ist. Lässt du zwei Rechner denselben Text in derselben Schrift und Größe zeichnen, unterscheidet sich das Anti-Aliasing um Haaresbreite. Hasht man die Pixel, erhält man einen Wert, der auf einem Gerät stabil und auf dem nächsten anders ist.

Die naheliegende Gegenmaßnahme ist Rauschen: ein paar Pixel verändern, bevor der Hash gebildet wird, damit der Wert sich ändert und nicht zum Tracking taugt. Fast jeder Antidetect-Browser bietet das an, meist mit der Bezeichnung „canvas: noise“.

Das Problem ist, dass der Canvas-Hash eines echten Browsers sich nicht ändert. Gleicher Rechner, gleicher Browser, gleiche Seite, gleiches Ergebnis — heute, morgen und nach einem Neustart. Genau diese Stabilität ist der Grund, warum das Signal überhaupt erhoben wird.

Ein Wert, der sich bei jeder Abfrage ändert, ist also nicht anonym geworden. Er ist auffällig geworden. Das ist derselbe Fehlertyp wie das Setzen von navigator.webdriver = false: Die ehrliche Antwort identifiziert dich, und die unehrliche identifiziert dich als jemanden, der lügt.

Wie die Erkennung tatsächlich funktioniert

Randomisierung zu erkennen erfordert nichts Raffiniertes. Vier Methoden, grob danach geordnet, wie oft sie in freier Wildbahn vorkommen:

Dasselbe zweimal zeichnen

Ein identisches Canvas im selben Seitenaufruf zweimal rendern und beides hashen. Echter Browser: identisch. Rauschen pro Aufruf: unterschiedlich. Das sind zwei Zeilen Code, und sie stehen in jedem ernstzunehmenden Erkennungsskript.

Das Rauschen wegmitteln

Ist das Rauschen zufällig und mittelwertfrei, treibt n-faches Rendern mit anschließender Mittelung das Ergebnis mit etwa √n gegen den wahren Wert. Zwanzig Renderdurchgänge reichen meist, um den zugrunde liegenden Hash zurückzugewinnen. Die Randomisierung hat den Tracker ein paar Millisekunden gekostet und dich trotzdem deine Anonymität — und dich obendrein als Randomisierer markiert.

Prüfen, ob das Rauschen dorthin gehört

Randomisiertes Canvas ist nicht per se verdächtig. Brave liefert es standardmäßig aus — sein „Farbling“ leitet das Rauschen aus einem Seed pro Sitzung und Origin ab. Tor Browser fragt stattdessen nach. Entscheidend ist, ob das Rauschen zu dem Browser passt, als der du dich ausgibst. Chrome randomisiert Canvas nicht. Ein Client, dessen User Agent Chrome 147 meldet und dessen Canvas sich pro Aufruf ändert, hat sich selbst widersprochen — und der Widerspruch ist der Befund, nicht das Rauschen.

Den Renderpfad variieren

Naive Umsetzungen hängen sich an toDataURL() und getImageData(). Es gibt andere Wege hinaus: toBlob(), createImageBitmap(), WebGL-readPixels(), OffscreenCanvas in einem Worker. Ist ein Pfad verrauscht und ein anderer nicht, widersprechen sich beide — und dieser Widerspruch wiegt schwerer als jeder einzelne Wert.

Streudiagramm der Abweichung des Canvas-Hashes, die sich beim Mitteln mehrerer Renderdurchgänge dem wahren Wert nähert
Mittelwertfreies Rauschen hebt sich selbst auf. Der Fehler fällt mit 1/√n, zwanzig Durchgänge reichen also meist, um den Wert zurückzuholen, den die Randomisierung verstecken sollte.

Besonders der Worker-Pfad

Der letzte Punkt verdient eine eigene Anmerkung, denn genau dort brechen die meisten Umsetzungen.

OffscreenCanvas lässt sich in einen Web Worker übertragen und dort rendern. Der Worker hat einen eigenen globalen Scope und eigene Kopien der Canvas-APIs, und eine Skriptinjektion auf Seitenebene erreicht ihn nicht. Wird dein Rauschen von eingeschleustem JavaScript angewendet, kommt das Canvas des Workers sauber heraus.

const off = new OffscreenCanvas(280, 60);
const w = new Worker(url);
w.postMessage({ canvas: off }, [off]);
// the worker draws and hashes — untouched by any page-level hook

Jetzt hat die Seite zwei Canvas-Hashes vom selben Gerät, die nicht zusammenpassen. Es gibt keine legitime Konfiguration, in der das vorkommt. Es ist dasselbe strukturelle Problem wie im CDP-Artikel beschrieben: Was von innerhalb der Seite angewendet wird, kann einen Kontext nicht erreichen, der der Seite nicht gehört.

Wie eine korrekte Umsetzung aussieht

Das Ziel ist nicht „jedes Mal ein anderer Wert“. Es ist ein anderer Wert als bei anderen Leuten, und jedes Mal derselbe Wert für mich. So formuliert ergeben sich die Anforderungen von selbst:

EigenschaftAnforderung
Innerhalb eines SeitenaufrufsBei jedem Render identisch
Über Sitzungen hinwegFür dasselbe Profil dauerhaft identisch
Über Profile hinwegUnterschiedlich, und nicht um einen konstanten Versatz
Über Renderpfade hinweg2D, WebGL, OffscreenCanvas und Worker stimmen überein
PlausibilitätPassend zur GPU, die das Profil behauptet

Der Mechanismus, der alle fünf erfüllt, ist ein Seed. Setze beim Anlegen des Profils einen zufälligen Seed, speichere ihn und leite die Pixelveränderung deterministisch daraus ab. Jeder Render dieses Profils liefert dieselbe Ausgabe, weil der Seed derselbe ist. Zwei Profile unterscheiden sich, weil ihre Seeds sich unterscheiden. Und weil die Ableitung dort geschieht, wo die Pixel entstehen — im Rasterisierer, nicht in einer gehookten JavaScript-Methode —, bekommt jeder Pfad dieselbe Behandlung, Worker eingeschlossen.

Dieser letzte Punkt ist der Grund, warum sich das außerhalb einer veränderten Engine kaum sauber umsetzen lässt. Es gibt keinen unterstützten Weg, aus einem Content-Skript an den Rasterisierer eines Workers heranzukommen.

Warum man es auch lassen kann

Es gibt ein ernstzunehmendes Argument, das man fair wiedergeben sollte, Canvas überhaupt nicht anzurühren.

Wenn du ein Profil pro physischem Rechner betreibst, ist dein echter Canvas-Hash ein völlig gewöhnlicher Wert eines völlig gewöhnlichen Computers. Er ist stabil, über alle Pfade stimmig und passt zu deiner GPU, weil er deine GPU ist. Nichts daran lädt zu einem zweiten Blick ein.

Das hört in dem Moment auf zu funktionieren, in dem du ein zweites Profil auf demselben Rechner startest. Jetzt hashen beide Profile identisch, und jede Plattform, die Canvas fingerprintet, kann sie verknüpfen — genau das Ergebnis, das du vermeiden wolltest.

Die Entscheidung lautet also nicht „Rauschen oder kein Rauschen“. Sie lautet:

Selbst testen

Dafür brauchst du keinen Erkennungsanbieter. Öffne auf einer beliebigen Seite die Browserkonsole und führe aus:

function h() {
  const c = document.createElement('canvas');
  const x = c.getContext('2d');
  x.textBaseline = 'top';
  x.font = '14px Arial';
  x.fillText('P8 canvas probe \u{1F511}', 2, 2);
  return c.toDataURL().slice(-32);
}
console.log(h(), h(), h());

Drei identische Zeichenketten liefert dir ein echter Browser. Drei verschiedene bedeuten Rauschen pro Aufruf — und dass jede Seite, die dieselben drei Zeilen ausführt, es weiß.

Führe es dann nach einem Neustart des Profils erneut aus. Der Wert sollte derselbe sein wie zuvor, wenn das Profil nach einem Gerät und nicht nach einem beweglichen Ziel aussehen soll — und anders als bei allen deinen übrigen Profilen. Gleiche das Ergebnis mit dem Rest des Fingerprints ab: Ein stabiles Canvas, das aber eine GPU nahelegt, die deine WebGL-Strings nicht behaupten, hat den Widerspruch bloß woanders hin verschoben.

Pro Profil stabil, zwischen Profilen verschieden

P8 leitet das Canvas jedes Profils aus einem beim Anlegen gesetzten Seed ab. Dasselbe Profil rendert immer gleich; zwei Profile nie.

Kostenlos starten
Alle Artikel Weiter: TLS- und HTTP/2-Fingerprinting