Zum Inhalt springen
HexaTransfer
Zurück zum Blog
Verschlusselung & Sicherheit

Web Crypto API Guide: native Browser-Verschlüsselung für Entwickler

Meistern Sie die Web Crypto API für verschlüsselte Transferanwendungen. Vollständiger Guide zu AES-GCM, RSA-OAEP und Schlüsselverwaltung.

Die Web Crypto API (spezifiziert in der W3C Web Cryptography API-Empfehlung, zugänglich über window.crypto.subtle) ist der browsernative Weg, Kryptografie durchzuführen, ohne eine Krypto-Bibliothek über das Netz nachladen zu müssen. Sie unterstützt AES-GCM, AES-CBC, AES-CTR, AES-KW, HMAC, RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDH, ECDSA, HKDF und PBKDF2 in allen modernen Browsern (Chrome 37+, Firefox 34+, Safari 10.1+, Edge 79+). Für Dateiübertragungsanwendungen ist das wichtig, weil jedes Byte Chiffretext clientseitig vor dem Upload erzeugt werden kann — mit einer konstant-zeitigen, auditierten Implementierung des Browsers. Dieser Guide behandelt die Primitive, die für verschlüsselten Dateitransfer relevant sind, und die Fallstricke, die jede erste Implementierung zu Fall bringen.

SubtleCrypto ist Promise-basiert und asynchron

Jede Methode auf crypto.subtle gibt ein Promise zurück. Das ist gewollt: Krypto-Operationen können auf Hardware oder Hintergrundthreads ausgelagert werden, daher verhindert die asynchrone API einen Missbrauch, der den Haupt-Thread blockieren würde. Codestruktur:

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true, // extrahierbar
  ["encrypt", "decrypt"]
);

Das zweite Argument (true) markiert den Schlüssel als extrahierbar, was bedeutet, er kann später über crypto.subtle.exportKey() exportiert werden. Für langlebige Schlüssel auf false setzen, um die Rohbytes für JavaScript unerreichbar zu halten. Für Schlüssel, die in ein URL-Fragment serialisiert werden müssen (das HexaTransfer-Muster), auf true setzen.

Das dritte Argument ist ein Key-Usages-Array. Ein Schlüssel, der mit ["encrypt"] erzeugt wurde, kann nicht zum Entschlüsseln verwendet werden, auch wenn AES-GCM symmetrisch ist. Diese Trennung verhindert, dass ein kompromittierter Verschlüsselungsfluss zum Entschlüsseln historischer Daten missbraucht wird.

AES-GCM für symmetrische Dateiverschlüsselung

AES-GCM ist das Arbeitstier für Dateiinhalte. Es bietet Authenticated Encryption with Associated Data (AEAD): Chiffretext plus Authentifizierungs-Tag plus optionale assoziierte Daten, die authentifiziert, aber nicht verschlüsselt werden. Für Dateiübertragungen einen 256-Bit-Schlüssel und einen 96-Bit-Nonce gemäß NIST SP 800-38D verwenden.

const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-Bit-Nonce
const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  key,
  plaintext
);

Die Ausgabe enthält ein 128-Bit-GCM-Authentifizierungs-Tag, das an den Chiffretext angehängt wird. Entschlüsselung verifiziert das Tag automatisch und wirft einen Fehler, wenn es nicht übereinstimmt. Nonce niemals mit demselben Schlüssel wiederverwenden; die Sicherheit von GCM bricht bei Nonce-Wiederverwendung katastrophal zusammen (Angreifer können den Authentifizierungsschlüssel wiederherstellen). Bei Dateiübertragungen, bei denen jede Datei einen frischen Schlüssel erhält, sind zufällige Nonces sicher; für langlebige Schlüssel einen Zähler verwenden.

PBKDF2 für passwortabgeleitete Schlüssel

Wenn Nutzende ein Passwort zum Schutz einer Datei eingeben, kann das Passwort nicht direkt als AES-Schlüssel verwendet werden. Zuerst durch PBKDF2 ableiten:

const passwordKey = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(password),
  "PBKDF2",
  false,
  ["deriveKey"]
);

const aesKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt: crypto.getRandomValues(new Uint8Array(16)),
    iterations: 600000,
    hash: "SHA-256",
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

OWASPs Passwort-Hashing-Leitfaden 2023 empfiehlt 600.000 Iterationen für PBKDF2-SHA-256. Alles unter 310.000 liegt unter der aktuellen Best Practice. Das Salt muss zufällig und zusammen mit dem Chiffretext gespeichert werden (es ist kein Geheimnis, muss aber eindeutig sein).

Für neuen Code 2026 Argon2id statt PBKDF2 in Betracht ziehen. Argon2 ist noch nicht in der Web Crypto API, aber Bibliotheken wie argon2-browser oder @noble/hashes bieten JavaScript/WASM-Implementierungen. Argon2id widersteht GPU-Angriffen wesentlich besser als PBKDF2.

RSA-OAEP für Schlüsselkapselung

Für Szenarien, in denen ein AES-Schlüssel einer Datei mit dem öffentlichen Schlüssel eines Empfängers verschlüsselt werden soll, RSA-OAEP verwenden. Schlüssel erzeugen:

const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]), // 65537
    hash: "SHA-256",
  },
  true,
  ["encrypt", "decrypt"]
);

Für neue Schlüssel modulusLength 4096 verwenden; 2048 ist noch akzeptabel, wird aber mit zunehmend konkreten Quantenzeitlinien schrittweise abgelöst. RSA-OAEP verschlüsselt nur kleine Payloads (höchstens modulusLength/8 - 2*hashLength - 2 Bytes), also einen 256-Bit-AES-Schlüssel kapseln statt Dateiinhalte direkt zu verschlüsseln.

Für leistungskritische Anwendungen ist ECDH mit P-256 oder P-384 eine bessere Alternative zu RSA. Schlüsselgenerierung ist eine Größenordnung schneller und Schlüsselgrößen deutlich kleiner.

Streaming für große Dateien

Eine 2-GB-Datei passt nicht bequem in einen Browser-ArrayBuffer. Chrome, Firefox und Safari ermöglichen das Lesen von Dateien mit File.stream(), das einen ReadableStream zurückgibt, und dann die Verarbeitung in Chunks. Die Web Crypto API selbst hat noch keine Streaming-Verschlüsselungs-/-entschlüsselungsmethoden (eine Lücke in der Spec), daher zwei Lösungsansätze:

  1. In Chunks aufteilen (64 KB oder 1 MB) und jeden mit einem eindeutigen Nonce verschlüsseln. Der Empfänger verkettet der Reihe nach. Dadurch geht echtes AEAD über die gesamte Datei verloren, funktioniert aber für die meisten Fälle.
  2. Eine WASM-Krypto-Bibliothek verwenden (libsodium.js, @noble/ciphers mit WASM-Backend), die Streaming-AEAD-Modi wie XChaCha20-Poly1305 oder AES-GCM-SIV unterstützt.

Für Transfers unter einigen hundert Megabyte funktioniert gepuffertes AES-GCM gut und ist wesentlich einfacher. Darüber wird Streaming notwendig, um Speicherdruck zu vermeiden.

Schlüsselexport, -import und URL-Fragmente

Für HexaTransfer-artige Flows, bei denen der Schlüssel im URL-Fragment reist:

const rawKey = await crypto.subtle.exportKey("raw", aesKey);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
// Share-URL wie https://example.com/f/abc123#key=keyBase64

URL-Fragmente werden in HTTP-Anfragen nie an Server gesendet (der Browser entfernt sie). Das hält den Schlüssel clientseitig, obwohl der Nutzer einen Link teilt. Auf Empfängerseite:

const keyBase64 = window.location.hash.slice(5); // "#key=" abschneiden
const rawKey = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
  "raw", rawKey, "AES-GCM", false, ["decrypt"]
);

Base64url-Kodierung verwenden (ersetze + mit -, / mit _, Padding entfernen), um URL-Kodierungsprobleme zu vermeiden.

Häufige Fallstricke

Math.random() für Salts oder IVs verwenden. Math.random() ist kryptografisch nicht sicher. Immer crypto.getRandomValues() verwenden.

IVs mit demselben Schlüssel wiederverwenden. GCMs Sicherheitseigenschaften brechen bei Nonce-Wiederverwendung vollständig zusammen. Zufällige 96-Bit-Nonces kollidieren nach ~2^48 Verschlüsselungen unter demselben Schlüssel (Geburtstagsgrenzen). Bei Dateiübertragungen, bei denen jede Datei einen eigenen Schlüssel hat, sicher; für langlebige Schlüssel einen Zähler verwenden.

HTTPS vergessen. crypto.subtle ist nur in sicheren Kontexten (HTTPS oder localhost) verfügbar. In einem unsicheren Ursprung ist crypto.subtle undefined.

Extrahierbare Schlüssel ungeschützt in IndexedDB speichern. Wenn Schlüssel persistiert werden müssen, sie zuerst kapseln (z. B. mit einem passphrasenabgeleiteten Schlüssel) bevor sie gespeichert werden. Niemals rohe AES-Schlüssel in localStorage speichern, das für jedes Skript im Ursprung zugänglich ist.

Nutzungspasswörtern ohne PBKDF2 vertrauen. Ein rohes Passwort in UTF-8-Bytes ist kein 256-Bit-Schlüssel. Immer ableiten.

Authentifizierungs-Tags nicht verifizieren. crypto.subtle.decrypt() tut das automatisch für AES-GCM, aber bei der Implementierung benutzerdefinierter Protokolle obendrauf die Prüfung nicht weglassen.

Browser-Kompatibilitätsnuancen

Alle wichtigen Browser unterstützen Web Crypto über HTTPS. Einige Besonderheiten:

  • Safaris PBKDF2 war jahrelang langsamer als Chrome/Firefox; der Unterschied schloss sich mit Safari 15.
  • Firefox erzwingt strengere Eingabevalidierung; Code, der in Chrome läuft, kann in Firefox OperationError werfen. In beiden testen.
  • Web Crypto in Service Workern funktioniert, erfordert aber einen HTTPS-Registrierungsbereich.
  • Node.js bietet require("crypto").webcrypto mit einer kompatiblen API seit Node 15, nützlich für isomorphen Krypto-Code.

Wann stattdessen eine Bibliothek verwenden

Web Crypto deckt die Grundlagen gut ab, fehlen aber moderne Primitive wie ChaCha20-Poly1305, Argon2, X25519 und Ed25519 (letzteres kommt). Dafür sind libsodium.js (via WASM) oder @noble/ciphers / @noble/curves (reines JavaScript, auditiert) die führenden Optionen. HexaTransfer verwendet Web Crypto-Primitive direkt für AES-GCM und PBKDF2, da diese den Dateiübertragungspfad ohne Abhängigkeiten abdecken.

Für einen vollständigen verschlüsselten Transferfluss kommt man mit Web Crypto in unter 100 Zeilen Code aus: AES-Schlüssel generieren (zufällig oder abgeleitet), Datei verschlüsseln, Chiffretext hochladen, Link mit Schlüssel im Fragment teilen, Empfänger importiert Schlüssel und entschlüsselt. Das ist der gesamte Ablauf.

Auf hexatransfer.com testen — kostenlos, ohne Konto, bis 10 GB.

Große Dateien sicher mit Ende-zu-Ende-Verschlüsselung senden

Übertragen Sie Dateien bis zu 10 GB kostenlos mit Ende-zu-Ende-Verschlüsselung. Kein Konto erforderlich. Ihre Dateien werden in Ihrem Browser verschlüsselt, bevor sie hochgeladen werden — niemand sonst kann sie lesen.

Datei senden