Zum Inhalt springen

Kryptographie der Seed-Speicherung im Browser

·6 Min. Lesezeit·- Aufrufe

Es gibt eine Spannung, die es wert ist, betrachtet zu werden: Eine BIP-39 Seed Phrase kodiert 256 Bit Entropie. Eine 6-stellige PIN hat ungefähr 20. Dennoch ist in den meisten Browser-Wallets die PIN das Tor zwischen einem Angreifer und der Seed. Wie lässt sich das verteidigen?

Die Antwort ist Key Stretching, und zu verstehen, warum es funktioniert, wo es bricht und was die Mathematik tatsächlich sagt, ist interessanter als die Implementierung.

Das Entropie-Problem

256 Bit Entropie bedeuten 2^256 mögliche Seeds. Brute-Forcing dieses Raums ist kein rechnerisches Problem, es ist ein physisches. Das beobachtbare Universum enthält ungefähr 10^80 Atome, der Suchraum ist 10^77. Die Materie geht aus, bevor die Kombinationen ausgehen.

Eine 6-stellige PIN hat 10^6 Kombinationen, etwa 20 Bit. Das ist ein völlig anderes Problem. Wenn ein Angreifer den verschlüsselten Blob stiehlt und PIN-Versuche mit beliebiger Geschwindigkeit testen kann, wird er den richtigen in unter einer Million Versuchen finden. Die Frage ist, wie teuer jeder Versuch ist. Das ist das gesamte Spiel.

Die zweischichtige Schlüsselhierarchie

Bevor wir in die Mathematik einsteigen, sieht die Architektur, die unter den Einschränkungen des Browsers standhält, so aus:

Zweischichtige Schlüsselhierarchie: PIN, PBKDF2, KEK, Device Key und verschlüsselte Seed

Zwei unabhängige Schichten. Eine zu brechen bricht nicht die andere. Der Grund für diese Trennung ist nicht nur Defense in Depth, sie hat eine praktische Konsequenz: Das Rotieren der PIN umhüllt nur den Device Key neu. Der Seed-Ciphertext auf der Festplatte wird nie berührt. Der Klartext-Seed muss während einer PIN-Änderung nicht in den Speicher gelangen.

PBKDF2: den Angreifer ausbremsen

PBKDF2, Password-Based Key Derivation Function 2, RFC 8018, nimmt ein Passwort mit niedriger Entropie und wendet iterativ eine pseudozufällige Funktion an, um einen kryptographischen Schlüssel zu erzeugen. Die Konstruktion für jeden Ausgabeblock ist:

U₁ = PRF(password, salt || i)
U₂ = PRF(password, U₁)
U₃ = PRF(password, U₂)
...
Uₙ = PRF(password, Uₙ₋₁)

block_i = U₁ ⊕ U₂ ⊕ ... ⊕ Uₙ

Wobei n die Iterationsanzahl und PRF HMAC-SHA-256 ist. Jede Auswertung von HMAC-SHA-256 umfasst zwei SHA-256-Kompressionen, sodass 600.000 PBKDF2-Iterationen 1.200.000 SHA-256-Operationen pro PIN-Versuch bedeuten.

Auf einem modernen CPU-Kern mit einem SHA-256-Durchsatz von ungefähr 250 MB/s dauert ein PBKDF2-Aufruf bei 600.000 Iterationen etwa 300 bis 500 ms. Ein Angreifer, der 10^6 PINs auf einem einzelnen Kern durchprobiert: 3 bis 6 Tage.

Das Salt ist entscheidend. Es ist ein zufälliger 128-Bit-Wert, der in der ersten PBKDF2-Runde enthalten ist. Zwei identische PINs auf verschiedenen Geräten erzeugen unterschiedliche Schlüssel. Vorberechnete Rainbow Tables werden nutzlos, da jedes Gerät ein einzigartiges Salt hat. Der Angreifer muss die vollständige PBKDF2-Berechnung für jedes Ziel von Grund auf durchführen.

Wo PBKDF2 versagt: GPUs

Das Problem mit PBKDF2 ist, dass es nicht memory-hard ist. Jeder HMAC-SHA-256-Aufruf verwendet eine kleine, feste Menge an Arbeitsspeicher, ungefähr 64 bis 128 Bytes. Das bedeutet, dass die Berechnung sich sauber über Tausende von GPU-Kernen parallelisieren lässt, ohne Einbußen.

CPU- vs. GPU-Parallelismus beim PBKDF2-PIN-Cracking

Eine Mittelklasse-GPU kann 10.000 oder mehr parallele SHA-256-Operationen ausführen. Bei 600.000 Iterationen und 10.000 parallelen Threads sinkt die Zeit pro Versuch um den Faktor 10.000. Was auf einer CPU 6 Tage dauerte, dauert auf einer GPU ungefähr 50 Sekunden. Das ist nicht theoretisch. Tools wie Hashcat haben GPU-beschleunigte PBKDF2-SHA-256-Implementierungen.

Die prinzipielle Lösung ist Argon2, der Gewinner der Password Hashing Competition 2015. Argon2id, die empfohlene Variante, erfordert eine konfigurierbare Speichermenge pro Berechnung. Bei 64 MB Speicher pro Thread kann eine GPU mit 8 GB VRAM nur ungefähr 128 parallele Instanzen ausführen. Der Speicherbedarf neutralisiert den Parallelisierungsvorteil.

Argon2 ist nicht Teil der Web Crypto API des Browsers. Eine WASM-Implementierung existiert und wird in Produktion von Tools wie KeeWeb verwendet, aber sie hinzuzufügen erhöht die Bundle-Größe und erweitert die Angriffsfläche in einem Kontext, in dem die Minimierung von ausführbarem Code selbst ein Sicherheitsanliegen ist. PBKDF2 mit hoher Iterationsanzahl ist die pragmatische Einschränkung der Arbeit innerhalb der nativen kryptographischen Schnittstelle des Browsers.

AES-GCM: warum Authentifizierung wichtig ist

Sobald man einen abgeleiteten Schlüssel hat, braucht man einen Verschlüsselungsmodus. AES-GCM, Galois/Counter Mode, ist die richtige Wahl, und zu verstehen warum, erfordert die Trennung zweier Eigenschaften, die oft verwechselt werden: Vertraulichkeit und Integrität.

AES-CBC bietet Vertraulichkeit. Jeder Klartextblock wird vor der Verschlüsselung mit dem vorherigen Ciphertext-Block XOR-verknüpft. Aber es bietet keine Integritätsgarantie. Ein Angreifer kann Bits im Ciphertext kippen und vorhersagbare Änderungen in der entschlüsselten Ausgabe erzeugen. Der Padding Oracle Attack nutzt dies aus: Durch Modifikation des Ciphertexts und Beobachtung, ob die Entschlüsselung gültiges oder ungültiges Padding erzeugt, kann ein Angreifer die gesamte Nachricht ohne den Schlüssel entschlüsseln. Die Lösung ist Encrypt-then-MAC, ein HMAC über den Ciphertext nach der Verschlüsselung, aber dies korrekt zu implementieren ist subtil. Der HMAC muss auch den IV abdecken, und der Vergleich muss in konstanter Zeit erfolgen. Hier scheitern die meisten Implementierungen.

AES-GCM hat die Integrität eingebaut. Es arbeitet in zwei simultanen Komponenten.

Der Counter Mode erzeugt Keystream-Blöcke durch Inkrementieren eines 32-Bit-Zählers, der an den 96-Bit-IV angehängt wird. Jeder Keystream-Block wird mit dem entsprechenden Klartextblock XOR-verknüpft. Strukturell ist dies ein Stream Cipher, daher gibt es keine Blockanforderung und kein Padding.

GHASH authentifiziert den Ciphertext durch Berechnung eines polynomialen Hashs in GF(2^128), dem Galois-Feld mit 2^128 Elementen, definiert über das irreduzible Polynom:

x¹²⁸ + x⁷ + x² + x + 1

Ein Hash-Subkey H = AES_K(0), die Verschlüsselung eines Nullblocks, dient als Feldelement für die Multiplikation. Jeder Ciphertext-Block wird akkumuliert:

X₀ = 0
X₁ = (X₀ ⊕ C₁) · H
X₂ = (X₁ ⊕ C₂) · H
...
Xₘ = (Xₘ₋₁ ⊕ Cₘ) · H
tag = (Xₘ ⊕ len_block) · H ⊕ ENC(K, IV || 0)

Der finale 128-Bit-Tag wird neben dem Ciphertext gespeichert. Bei der Entschlüsselung wird der Tag neu berechnet und verglichen. Jede Modifikation am Ciphertext, am IV oder an den zusätzlich authentifizierten Daten erzeugt einen anderen Tag. Die Entschlüsselung schlägt fehl, bevor Klartext zurückgegeben wird.

Eine harte Einschränkung: Der IV muss für jede Verschlüsselung unter demselben Schlüssel einzigartig sein. Wenn dasselbe Schlüssel-IV-Paar zweimal verwendet wird, kann ein Angreifer die beiden Ciphertexte XOR-verknüpfen und den Keystream aufheben, wodurch eine Beziehung zwischen den beiden Klartexten wiederhergestellt wird. Für jede Verschlüsselungsoperation muss ein frischer, kryptographisch zufälliger IV erzeugt werden.

Verschlüsselungs- und Entschlüsselungsflüsse

Verschlüsselungs- und Entschlüsselungssequenz für die Seed

Der Entschlüsselungspfad markiert den entpackten Device Key als non-extractable innerhalb der Web Crypto API. Schlüsselmaterial innerhalb von SubtleCrypto ist für JavaScript opak. Der Schlüssel kann für Operationen verwendet, aber von keinem Script gelesen werden, auch nicht von einem per XSS injizierten. Der Mnemonic-Buffer wird in einem finally-Block sofort nach Abschluss der Signierungsoperation auf null gesetzt.

Was die Mathematik ehrlich sagt

Das Schema ist solide innerhalb seiner Einschränkungen. Das schwächste Glied ist nicht die Kryptographie, es ist die PIN-Entropie, und keine Iterationsanzahl kompensiert vollständig 20 Bit Eingabe.

Bei 600.000 PBKDF2-Iterationen braucht ein CPU-Angreifer Tage. Ein GPU-Angreifer braucht Stunden. Die Mitigation, die dies tatsächlich löst, ist WebAuthn-bound Key Storage: einen Platform Authenticator als KDF-Tor zu verwenden. Die Credential lebt in einem Secure Enclave, der private Schlüssel verlässt nie die Hardware, und das Brute-Forcen eines biometrischen Merkmals ist kein praktikabler Angriff. Browser- und Plattformunterstützung ist inkonsistent. Die Implementierungsoberfläche ist real.

Die Obergrenze ist die PIN. Das ist es wert, explizit gemacht zu werden, anstatt es hinter Parameterwahlen zu verstecken.

Bis bald.