Zum Inhalt springen
HexaTransfer
Zurück zum Blog
Technische Vertiefungen

Web Crypto API: Komplettes Tutorial zur Dateiverschlüsselung

Meistern Sie die Web Crypto API für browserbasierte Dateiverschlüsselung: AES-GCM, RSA-OAEP und Schlüsselmanagement in JavaScript-Anwendungen.

Die Web Crypto API verschlüsselt Dateien direkt im Browser über native window.crypto.subtle-Methoden — ohne externe Bibliotheken. Das BSI klassifiziert AES-256-GCM als zugelassenes Verfahren für den Schutz von Verschlusssachen bis zum Geheimhaltungsgrad VS-NfD; dieselbe Kryptographie steht Entwicklern heute im Browser nativ zur Verfügung. Für die Dateiverschlüsselung leitet man einen Schlüssel aus einem Passwort via PBKDF2 ab (210.000 Iterationen, SHA-256), verschlüsselt die Datei-Bytes dann mit AES-GCM mit einem 96-Bit-IV und 128-Bit-Auth-Tag. Öffentliche-Schlüssel-Workflows nutzen RSA-OAEP mit 4096-Bit-Schlüsseln zum Wrapping des symmetrischen Schlüssels. Die API ist über HTTPS in jedem modernen Browser verfügbar und läuft im nativen Krypto-Backend statt in JavaScript.

Warum SubtleCrypto Pure-JS-Bibliotheken schlägt

window.crypto.subtle ruft das geprüfte native Krypto-Backend des Browsers auf — typischerweise BoringSSL in Chromium oder CommonCrypto in Safari. Im Vergleich zu Pure-JS-Optionen wie CryptoJS oder sjcl läuft SubtleCrypto 30- bis 80-mal schneller für AES-GCM, vermeidet Timing-Seitenkanäle in JavaScript-Interpretern und liefert null Bytes an Nutzer aus. Der Tradeoff ist eine Promise-basierte API, die nur mit ArrayBuffer und CryptoKey-Objekten arbeitet, sodass man viel Zeit mit dem Mischen von Uint8Array, Blob und ReadableStream verbringt. Für Dateigrößen über 100 MB ist dieses Plumbing wichtiger als die rohe Krypto-Geschwindigkeit.

Einen Schlüssel aus einem Passwort mit PBKDF2 ableiten

Niemals ein Passwort direkt als AES-Schlüssel verwenden. Stattdessen das Passwort als Rohmaterial importieren und dann einen 256-Bit-Schlüssel ableiten:

async function deriveKey(password, salt) {
  const enc = new TextEncoder();
  const material = await crypto.subtle.importKey(
    'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']
  );
  return crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 210000, hash: 'SHA-256' },
    material,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
}

OWASPs Leitfaden für 2026 empfiehlt mindestens 600.000 Iterationen mit SHA-256, wobei 210.000 für risikoarme Kontexte akzeptabel bleibt. Einen frischen 16-Byte-Salt pro Datei mit crypto.getRandomValues generieren und ihn neben dem Ciphertext speichern. Argon2id wäre stärker, ist aber noch nicht von SubtleCrypto exponiert.

Eine Datei mit AES-GCM verschlüsseln

AES-GCM liefert Vertraulichkeit und Authentizität in einem Durchgang. Die kritische Regel: niemals ein (key, IV)-Paar wiederverwenden:

async function encryptFile(file, key) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const plaintext = await file.arrayBuffer();
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv, tagLength: 128 },
    key,
    plaintext
  );
  return { iv, ciphertext };
}

Für eine 2-GB-Datei alloziert file.arrayBuffer() den gesamten Buffer, was auf Mobile Safari oft crasht. Die Datei in 4-MB-Chunks aufteilen, jeden mit einem eindeutigen IV verschlüsseln, der aus einem Zähler plus einem zufälligen Präfix abgeleitet wird, und ein Versionsbyte plus Salt voranstellen, damit der Entschlüsseler weiß, womit er es zu tun hat.

Große Dateien durch TransformStream streamen

Um den Speicher-Blowup zu vermeiden, Verschlüsselung in einem TransformStream wrappen und die Datei durchleiten:

const chunkSize = 4 * 1024 * 1024;
const encryptor = new TransformStream({
  async transform(chunk, controller) {
    const iv = nextIV(counter++);
    const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, chunk);
    controller.enqueue(new Uint8Array([...iv, ...new Uint8Array(ct)]));
  }
});
await file.stream()
  .pipeThrough(sliceByChunks(chunkSize))
  .pipeThrough(encryptor)
  .pipeTo(uploadSink);

file.stream() gibt einen ReadableStream<Uint8Array> zurück, der faul von der Disk liest. Der Slicer produziert Fixed-Size-Chunks, sodass GCM-Tags vorhersehbar ausgerichtet sind. Peak-Speicher bleibt unter 20 MB selbst für ein 10-GB-Upload.

Den symmetrischen Schlüssel mit RSA-OAEP wrappen

Wenn man eine Datei mit einem bestimmten Empfänger teilen muss, einmal ein RSA-Schlüsselpaar generieren und den öffentlichen Schlüssel veröffentlichen:

const keypair = await crypto.subtle.generateKey(
  { name: 'RSA-OAEP', modulusLength: 4096,
    publicExponent: new Uint8Array([1,0,1]), hash: 'SHA-256' },
  true, ['wrapKey', 'unwrapKey']
);

Einen AES-GCM-Schlüssel für die Datei generieren, dann wrappen:

const wrapped = await crypto.subtle.wrapKey(
  'raw', fileKey, keypair.publicKey,
  { name: 'RSA-OAEP' }
);

4096-Bit-RSA-Schlüssel bieten laut NIST SP 800-57 bis 2030 etwa 150-Bit-Sicherheit. Für Forward Secrecy oder Post-Quanten-Resistenz RSA-OAEP mit ECDH über P-384 kombinieren oder zu ML-KEM (Kyber) migrieren, sobald die WebCrypto-Arbeitsgruppe es unterstützt.

Schlüssel sicher in IndexedDB speichern

CryptoKey-Objekte sind standardmäßig nicht extrahierbar, was bedeutet, dass man sie in IndexedDB persistieren kann, ohne die Roh-Bytes jemals JavaScript auszusetzen:

const db = await openDB('keystore', 1);
await db.put('keys', keypair.privateKey, 'user-signing-key');

Browser serialisieren den Schlüssel mit dem strukturierten Klon-Algorithmus und halten die eigentlichen Bytes im Krypto-Backend. Ein kompromittiertes Script kann encrypt oder decrypt mit dem gespeicherten Schlüssel aufrufen, aber dessen Material nicht auslesen. Das ist eine bedeutende Härtungsmaßnahme gegenüber dem Speichern von Base64-Schlüsseln in localStorage.

Fehler behandeln, die die API wirft

SubtleCrypto wirft OperationError bei Fehlern bei der authentifizierten Entschlüsselung — was typischerweise bedeutet, dass der Ciphertext manipuliert wurde, der IV falsch ist oder der Nutzer das falsche Passwort eingegeben hat. Es wirft DataError, wenn der Eingabe-Buffer die falsche Länge hat, NotSupportedError, wenn der Algorithmus nicht implementiert ist, und InvalidAccessError, wenn der Schlüssel nicht mit den richtigen Usage-Flags importiert wurde. Entschlüsselung immer in try/catch wrappen, eine neutrale „Datei konnte nicht entschlüsselt werden"-Meldung anzeigen und vermeiden, preiszugeben, ob das Tag oder die Struktur fehlschlug.

Praxisfallen in der Produktion

Firefox auf Android begrenzt deriveKey-Iterationen auf etwa 1 Million, bevor der UI-Thread für mehrere Sekunden einfriert — Schlüsselableitung in einem dedizierten Worker ausführen. Safari unter 16.4 unterstützt crypto.subtle.verify mit PSS-Padding nicht. Chrome drosselt getRandomValues-Aufrufe über 64 KB pro Aufruf, also in einer Schleife iterieren, wenn mehr Entropie benötigt wird. ArrayBuffer-Transfers durch postMessage sind Zero-Copy, aber trennen das Original, was Entwickler oft überrascht.

HexaTransfer nutzt genau diese AES-GCM- plus PBKDF2-Pipeline für jeden Upload — Schlüssel werden in einem Worker abgeleitet, Ciphertext wird zum Storage gestreamt, ohne dass der Server jemals Plaintext sieht. Ende-zu-Ende-Verschlüsselung im technischen Sinne: der Schlüssel verlässt den Browser des Senders nie unverschlüsselt.

Jetzt ausprobieren: https://hexatransfer.com — kostenlos, kein Account, bis zu 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