Web Crypto API gids: native browserencryptie voor ontwikkelaars
Beheers de Web Crypto API voor versleutelde overdrachtsapplicaties. Complete gids voor AES-GCM, RSA-OAEP en sleutelbeheer.
De Web Crypto API — gespecificeerd in de W3C Web Cryptography API aanbeveling en beschikbaar via window.crypto.subtle — is de browsernatieve manier om cryptografie uit te voeren zonder een externe cryptografiebibliotheek te laden. De API ondersteunt AES-GCM, AES-CBC, AES-CTR, AES-KW, HMAC, RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDH, ECDSA, HKDF en PBKDF2 in alle moderne browsers (Chrome 37+, Firefox 34+, Safari 10.1+, Edge 79+). Voor bestandsoverdrachtsapplicaties is dit van grote waarde: elke byte van de ciphertext kan client-side worden gegenereerd vóór de upload, terwijl de browser een constant-time, geauditeerde implementatie biedt. Deze gids behandelt de primitieven die relevant zijn voor versleutelde bestandsoverdracht, inclusief de valkuilen die elke eerste implementatie onderuit halen.
SubtleCrypto is Promise-gebaseerd en asynchroon
Elke methode op crypto.subtle geeft een Promise terug. Dat is bewust: cryptooperaties kunnen worden uitbesteed aan hardware of achtergrondthreads, zodat de async-verplichting voorkomt dat de API op manieren wordt misbruikt die de main thread zouden blokkeren. Codestructuur:
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // extractable
["encrypt", "decrypt"]
);
Het tweede argument (true) markeert de sleutel als extracteerbaar, zodat deze later kan worden geëxporteerd via crypto.subtle.exportKey(). Voor langlevende sleutels stel je dit in op false om de ruwe bytes onbereikbaar te houden vanuit JavaScript. Voor sleutels die je moet serialiseren naar een URL-fragment (het HexaTransfer-patroon) stel je het in op true.
Het derde argument is een key-usages array. Een sleutel gegenereerd met ["encrypt"] kan niet worden gebruikt om te ontsleutelen, ook al is AES-GCM symmetrisch. Deze scheiding voorkomt dat een gecompromitteerde encryptiestroom kan worden misbruikt om historische data te ontsleutelen.
AES-GCM voor symmetrische bestandsversleuteling
AES-GCM is het werkpaard voor bestandsinhoud. Het biedt geauthenticeerde versleuteling met bijbehorende data (AEAD): ciphertext plus authenticatietag plus optionele bijbehorende data die geauthenticeerd maar niet versleuteld is. Voor bestandsoverdracht gebruik je een 256-bits sleutel en een 96-bits nonce per NIST SP 800-38D aanbevelingen.
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit nonce
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
plaintext
);
De uitvoer bevat een 128-bits GCM-authenticatietag die aan de ciphertext is toegevoegd. Ontsleuteling verifieert de tag automatisch en gooit een fout als deze niet klopt. Hergebruik nooit een nonce met dezelfde sleutel; de beveiliging van GCM breekt volledig bij nonce-hergebruik (aanvallers kunnen de authenticatiesleutel herstellen). Voor bestandsoverdracht waarbij elk bestand een nieuwe sleutel krijgt, zijn willekeurige nonces veilig; voor langlevende sleutels gebruik je een teller.
PBKDF2 voor wachtwoordafgeleide sleutels
Wanneer gebruikers een wachtwoord invoeren om een bestand te beveiligen, kun je het wachtwoord niet direct als AES-sleutel gebruiken. Verwerk het eerst via PBKDF2:
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"]
);
OWASP's 2023 wachtwoord-hash-richtlijnen bevelen 600.000 iteraties aan voor PBKDF2-SHA-256. Alles onder de 310.000 voldoet niet aan de huidige best practices. De salt moet willekeurig zijn en worden opgeslagen naast de ciphertext (niet geheim, maar moet uniek zijn).
Voor nieuwe code in 2026 overweeg je Argon2id in plaats van PBKDF2. Argon2 zit nog niet in de Web Crypto API, maar bibliotheken als argon2-browser of @noble/hashes bieden JavaScript/WASM-implementaties. Argon2id biedt veel betere weerstand tegen GPU-aanvallen dan PBKDF2.
RSA-OAEP voor sleutelverpakking
Voor scenario's waarbij je de AES-sleutel van een bestand wilt versleutelen met de publieke sleutel van een ontvanger, gebruik je RSA-OAEP. Sleutelparen genereren:
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: "SHA-256",
},
true,
["encrypt", "decrypt"]
);
Gebruik modulusLength 4096 voor nieuwe sleutels; 2048 is acceptabel maar zal worden gefaseerd naarmate de kwantumtijdlijnen duidelijker worden. RSA-OAEP versleutelt alleen kleine payloads (maximaal modulusLength/8 - 2*hashLength - 2 bytes), dus verpak een 256-bits AES-sleutel in plaats van bestandsinhoud direct te versleutelen.
Voor prestatiegevoelige apps is ECDH met P-256 of P-384 een beter alternatief voor RSA. Sleutelgeneratie is een orde van grootte sneller en sleutelgroottes zijn veel kleiner.
Streaming voor grote bestanden
Een bestand van 2 GB past niet comfortabel in een browser ArrayBuffer. Chrome, Firefox en Safari laten je bestanden lezen via File.stream() dat een ReadableStream teruggeeft, waarna je in stukken kunt verwerken. De Web Crypto API heeft zelf nog geen streaming encryptie-/ontsleutelingsmethoden (een leemte in de specificatie), dus twee oplossingen:
- Splits in stukken (64 KB of 1 MB) en versleutel elk met een unieke nonce. De ontvanger voegt ze aaneengesloten samen. Dit gaat ten koste van echte AEAD op het hele bestand, maar werkt voor de meeste gevallen.
- Gebruik een WASM cryptobibliotheek (libsodium.js, @noble/ciphers met WASM-backend) die streaming AEAD-modi ondersteunt zoals XChaCha20-Poly1305 of AES-GCM-SIV.
Voor overdrachten onder een paar honderd megabytes werkt gebufferde AES-GCM prima en is veel eenvoudiger. Daarboven wordt streaming noodzakelijk om geheugendruk te vermijden.
Sleutelexport, -import en URL-fragmenten
Voor HexaTransfer-stijl stromen waarbij de sleutel in het URL-fragment reist:
const rawKey = await crypto.subtle.exportKey("raw", aesKey);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
// Deel URL zoals https://example.com/file/abc123#key=keyBase64
URL-fragmenten worden nooit naar servers gestuurd in HTTP-verzoeken (de browser verwijdert ze). Dit houdt de sleutel client-side zelfs als de gebruiker een link deelt. Aan de ontvangstzijde:
const keyBase64 = window.location.hash.slice(5); // strip "#key="
const rawKey = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
"raw", rawKey, "AES-GCM", false, ["decrypt"]
);
Gebruik base64url-codering (vervang + door -, / door _, verwijder opvulling) om URL-coderingsproblemen te vermijden.
Veelvoorkomende valkuilen
Math.random() gebruiken voor salts of IV's. Math.random() is niet cryptografisch veilig. Gebruik altijd crypto.getRandomValues().
IV's hergebruiken met dezelfde sleutel. De beveiligingseigenschappen van GCM breken volledig bij nonce-hergebruik. Willekeurige 96-bits nonces botsen na ~2^48 versleutelingen onder dezelfde sleutel (verjaardagsgrens). Voor bestandsoverdracht waarbij elk bestand zijn eigen sleutel heeft, is dit veilig; voor langlevende sleutels gebruik je een teller.
HTTPS vergeten. crypto.subtle is alleen beschikbaar in beveiligde contexten (HTTPS of localhost). Op een onveilige origin is crypto.subtle undefined.
Extracteerbare sleutels onbeschermd opslaan in IndexedDB. Als je sleutels moet bewaren, verpak ze dan (bijv. met een wachtzin-afgeleide sleutel) voordat je ze opslaat. Sla nooit ruwe AES-sleutels op in localStorage, dat toegankelijk is voor elk script op de origin.
Gebruikersverstrekte wachtwoorden vertrouwen zonder PBKDF2. Een ruw wachtwoord omgezet naar UTF-8-bytes is geen 256-bits sleutel. Leid altijd af.
Authenticatietags niet verifiëren. crypto.subtle.decrypt() doet dit automatisch voor AES-GCM, maar als je aangepaste protocollen bovenop bouwt, sla de controle dan niet over.
Browserondersteuning en nuances
Alle grote browsers ondersteunen Web Crypto op HTTPS. Enkele eigenaardigheden:
- Safari's PBKDF2 was jarenlang langzamer dan Chrome/Firefox; het verschil is gedicht in Safari 15.
- Firefox hanteert strengere invoervalidatie; code die werkt in Chrome kan
OperationErrorgooien in Firefox. Test in beide. - Web Crypto in service workers werkt, maar vereist dat het registratiebereik HTTPS is.
- Node.js biedt
require("crypto").webcryptomet een compatibele API sinds Node 15, handig voor isomorfe cryptocode.
Wanneer je een bibliotheek gebruikt in plaats hiervan
Web Crypto dekt de basis goed, maar mist moderne primitieven zoals ChaCha20-Poly1305, Argon2, X25519 en Ed25519 (al wordt Ed25519 toegevoegd). Hiervoor zijn libsodium.js (via WASM) of @noble/ciphers / @noble/curves (pure JavaScript, geauditeerd) de toonaangevende opties. HexaTransfer gebruikt Web Crypto primitieven direct voor AES-GCM en PBKDF2, omdat die het pad van bestandsoverdracht afdekken zonder afhankelijkheden.
Voor een volledige versleutelde overdrachtstroom brengt Web Crypto je er in minder dan 100 regels code: genereer AES-sleutel, leid af of willekeurig, versleutel bestand, upload ciphertext, deel link met sleutel in fragment, ontvanger importeert sleutel en ontsleutelt. Dat is het geheel.
Probeer het op hexatransfer.com — gratis, zonder account, tot 10 GB.
Verstuur grote bestanden veilig met end-to-end-versleuteling
Draag bestanden tot 10 GB gratis over met end-to-end-versleuteling. Geen account nodig. Uw bestanden worden in uw browser versleuteld voordat ze worden geüpload — niemand anders kan ze lezen.
Een bestand verzenden