Tech · Tools · Privacy

Warum unser Stegofile-Tool mit Brave nicht funktionierte

Canvas-Fingerprinting, Brave Shields und wie das Problem gelöst wurde

13. Juni 2026, 13:14 Uhr UTC · 5 Min.
Auch auf Englisch verfügbar: Read in English
Warum unser Stegofile-Tool mit Brave nicht funktionierte

Der Stegofile-Concealer ist ein Tool, um Dateien und Text mit Hilfe von LSB-Steganografie und optionaler AES-GCM-Verschlüsselung in PNG-Bilder zu verstecken. Danke an meine Besucher Xandra und Dave B., die mich auf einen Bug im Script aufmerksam gemacht haben. Tatsächlich schlug trotz vermeintlich korrekter Verschlüsselung und steganografischer Verpackung das Entpacken manchmal fehl. Die Ausgabedatei war schlicht korrupt.

Nach ein wenig Probieren kam ich der Sache auf die Spur und konnte das Problem nachstellen. Die Ursache liegt tief in einem Browser-Datenschutzmechanismus, den Brave standardmäßig bei aktivierten Shields gegen Canvas-Fingerprinting einsetzt.

Was ist Canvas-Fingerprinting?

Also eigentlich eine gute Sache. Aber was genau macht Canvas-Fingerprinting im Kontext von Browser-Fingerprinting? Nun, das ist eine Methode, um Nutzer wiederzuerkennen, ohne Cookies zu setzen. Dabei führen Websites JavaScript aus, das den Browser testet oder vermisst. Bildschirmauflösung, installierte Schriftarten, Betriebssystem, GPU. Diese Kombination ist oft einzigartig genug, um einen Fingerabdruck zu bilden. Nun, dass so etwas gemacht wird, wissen wir ja schon. Aber es geht noch weiter.

Canvas-Fingerprinting ist eine besonders effektive Variante davon. Diese Technik nutzt das HTML-Canvas-Element zum Zeichnen. Ein Skript zeichnet heimlich einen Text oder eine Form auf ein unsichtbares Canvas und liest die Pixel anschließend per getImageData() aus. Der Trick dabei ist, dass aufgrund unterschiedlicher GPUs, Treiber, Betriebssystem-Rendering-Engines und Schriftarten die Pixel auf jedem Gerät minimal anders aussehen. Für das menschliche Auge nicht sichtbar, aber einzigartig genug und vor allem messbar und reproduzierbar.

// Klassisches Canvas-Fingerprinting
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.fillText('Sphinx of black quartz', 10, 10);
var fingerprint = canvas.toDataURL(); // eindeutig pro Gerät

Das Bild als solches spielt dabei keine Rolle. Websites, zum Beispiel Tracking-Pixel setzende Firmen, zeichnet selbst etwas und liesen es selbst wieder aus. Es geht ausschließlich darum, wie dein Browser und deine Hardware das Rendering durchführen, um dich so über verschiedene Webseiten verfolgen zu können.

Was Brave dagegen tut

Man kann von Brave halten was man will, und ich verlinke hier bewusst nicht auf sie. Doch was Brave macht, ist wirksam. Es setzt dem Canvas-Fingerprinting einen einfachen aber wirksamen Riegel vor. Alle Rückgabewerte von getImageData(), toDataURL() und toBlob() werden mit minimalem deterministischem Noise versehen. Der Noise basiert auf einem zufälligen Seed pro Session und Origin. Die Websites bekommen also immer leicht verfälschte Pixelwerte zurück und können keinen stabilen Fingerabdruck mehr aufbauen.

Aus Datenschutzsicht eine elegante Lösung. Aus Entwicklersicht ist das weniger angenehm, ein zweischneidiges Schwert, das die Arbeit erschwert.

Das Problem mit Steganografie

Wie im Tool und dem dazugehörigen Blogpost bereits beschrieben, funktioniert die LSB-Steganografie, indem das niederwertigste Bit jedes Farbkanals pro Pixel manipuliert wird. Ein rotes Pixel mit Wert 200 wird zu 201, wenn ein gespeichertes Bit eine 1 ist, oder bleibt 200, wenn es eine 0 ist. Der visuelle Unterschied ist für das menschliche Auge unsichtbar. Die Methode hat eine eiserne Grundvoraussetzung. Was reingeschrieben wird, muss auch wieder rauskommen. Pixelgenauigkeit ist keine Komfortfunktion, sie ist Pflicht.

Brave und andere Browser mit Canvas-Fingerprinting-Schutz denken dabei nicht in Anwendungsfällen. Sie sehen getImageData() und fügen stumpf Noise hinzu. Sie sehen toBlob(), bäm, und es kommt wieder Datenmüll hinzu, um den Fingerprint kaputt zu machen. Ob es sich tatsächlich um Fingerprinting oder um legitime Pixel-Verarbeitung handelt, wird nicht unterschieden. Wie auch?

Konkret passierte in unserem Fall Folgendes.

Beim Einbetten wurde das Trägerbild über drawImage() auf ein Canvas gezeichnet und mit getImageData() ausgelesen. Die zurückgegebenen Pixel enthielten bereits den Noise und unsere eingebetteten Bits wurden über einen bereits leicht verfälschten Pixel geschrieben. Beim Speichern rauschte toBlob() die Ausgabedatei dann nochmal zusätzlich. Das heruntergeladene PNG hatte gleich an mehreren Stellen falsche Pixelwerte, was beim Entpacken zu einem AES-GCM-Authentifizierungsfehler führte, weil der Auth-Tag nicht mehr passte.

Decryption failed (OperationError) - flags=0x3 payloadLen=103675

Der Self-Test, der ausschließlich im Speicher ablief, bestand trotzdem. Er berührte nie eine Canvas-API. Die fertige Datei aber war unbrauchbar. Dafür Sorry, wenn ihr als Geheimagent jetzt alle Daten verloren habt, mea culpa, ihr solltet euch sowieso einen anderen Job suchen.

Die Lösung: Canvas-API komplett umgehen

Der einzige saubere Weg war, die Canvas-API für beide kritischen Operationen zu ersetzen.

Ausgabe: Statt canvas.toBlob() schreiben wir das PNG jetzt direkt in JavaScript. Ein eigener Encoder baut die Datei Byte für Byte zusammen. PNG-Signatur, IHDR-Chunk mit Bildgröße und Farbtyp, IDAT-Chunk mit den Pixeldaten in DEFLATE Stored Blocks (unkomprimiert, dafür vollständig unter unserer Kontrolle), CRC32 und Adler-32 Prüfsummen kommen nun aus dem Script. Kein Browser-API wird involviert, ergo ist kein Noise mehr möglich.

Eingabe: Statt drawImage() + getImageData() nutzt das Script jetzt die ImageDecoder-API aus dem WebCodecs-Standard. Sie dekodiert PNG-Dateien direkt in den Arbeitsspeicher und liefert die Rohdaten per VideoFrame.copyTo(), ein direkter Speicherkopiervorgang ohne den Canvas-Umweg. Ohne den Fingerprinting-Schutz vollständig auseinandergenommen zu haben, hat es für unseren Anwendungsfall ausschließlich Canvas-APIs betroffen und glücklicherweise nicht die WebCodecs. Als Fallback für ältere Browser ohne WebCodecs-Unterstützung bleibt der klassische Canvas-Weg erhalten.

Einbetten:  ImageData → reiner JS-PNG-Encoder → Blob
Entpacken:  Blob → ImageDecoder → VideoFrame.copyTo → ImageData

Wichtig ist, immer sofort den Rückweg zu testen, bevor man Bilder verschickt, deren versteckter Inhalt sonst nicht zu entpacken ist.

Was bleibt

Canvas-Fingerprinting ist ein echtes Tracking-Problem und Braves Schutz dagegen ist berechtigt. Unsere Lösung ist kein Angriff auf den Datenschutz, sondern die technisch korrekte Antwort auf ein API-Design, das für Rendering gedacht ist und nicht für pixelgenaue Datenverarbeitung.

Warum man Daten nicht nur verschlüsseln, sondern auch verstecken muss, steht in Alice und Bob werden nun rechtmäßig vom Dreibuchstabenamt beobachtet.

Den Stegofile-Concealer findet ihr an gewohnter Stelle, jetzt mit Versionsnummer im Footer. Wenn ihr ihn euch schon heruntergeladen habt, müsst ihr ihn erneut laden. Version 0.1.15 läuft. Bitte testet und reportet weiter! Danke an Euch.

Peace Out Alexander