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.
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:
| Eigenschaft | Anforderung |
|---|---|
| Innerhalb eines Seitenaufrufs | Bei jedem Render identisch |
| Über Sitzungen hinweg | Für dasselbe Profil dauerhaft identisch |
| Über Profile hinweg | Unterschiedlich, und nicht um einen konstanten Versatz |
| Über Renderpfade hinweg | 2D, WebGL, OffscreenCanvas und Worker stimmen überein |
| Plausibilität | Passend 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:
- Eine Identität pro Rechner — Canvas in Ruhe lassen. Echte Hardware, echter Hash, nichts zu erklären.
- Mehrere Identitäten pro Rechner — dann brauchst du Canvas-Werte je Profil, und sie müssen stabil und stimmig sein. Randomisierung pro Aufruf ist die schlechteste der drei Optionen, weil sie dich weder verbirgt noch die Verknüpfung verhindert.
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.