Web Crypto API: complete tutorial voor bestandsversleuteling
Beheers de Web Crypto API voor bestandsversleuteling in de browser. AES-GCM, RSA-OAEP en sleutelbeheer in JavaScript-toepassingen uitgelegd.
De Web Crypto API stelt u in staat bestanden rechtstreeks in de browser te versleutelen via de native window.crypto.subtle-methoden, zonder externe bibliotheken. Volgens de AVG (Algemene Verordening Gegevensbescherming) bent u als verwerkingsverantwoordelijke verplicht passende technische beveiligingsmaatregelen te treffen — browsergebaseerde versleuteling is precies zo'n maatregel. De standaardpipeline: leid een sleutel af uit een wachtwoord via PBKDF2 (210.000 iteraties, SHA-256), versleutel bestandsdata met AES-256-GCM en een 96-bit IV, en verwerk asymmetrische workflows met RSA-OAEP. De API werkt over HTTPS in elke moderne browser en draait in de native crypto-backend — niet in JavaScript.
Waarom SubtleCrypto beter is dan pure JavaScript-bibliotheken
window.crypto.subtle roept de geauditeerde native crypto-backend aan — doorgaans BoringSSL in Chromium of CommonCrypto op Safari. Vergeleken met pure-JavaScript-opties zoals CryptoJS of sjcl is SubtleCrypto 30-80 keer sneller voor AES-256-GCM, vermijdt het timing-zijkanalen in de JavaScript-interpreter en levert het nul extra bytes aan gebruikers. De keerzijde is een op Promise gebaseerde API die uitsluitend werkt met ArrayBuffer en CryptoKey-objecten, waardoor u veel tijd kwijt bent aan conversies tussen Uint8Array, Blob en ReadableStream. Voor bestanden boven 100 MB weegt die overhead zwaarder dan de pure crypto-snelheid.
Een sleutel afleiden uit een wachtwoord met PBKDF2
Gebruik een wachtwoord nooit rechtstreeks als AES-sleutel. Importeer het wachtwoord als grondstof en leid een 256-bit sleutel af:
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']
);
}
OWASP adviseert voor 2026 minimaal 600.000 iteraties met SHA-256, hoewel 210.000 acceptabel blijft voor lage-risicocontexten. Genereer een verse 16-byte salt per bestand met crypto.getRandomValues en sla die op naast de ciphertekst. Argon2id zou sterker zijn, maar is nog niet beschikbaar via SubtleCrypto.
Een bestand versleutelen met AES-GCM
AES-256-GCM biedt vertrouwelijkheid én authenticiteit in één stap. De cruciale regel: hergebruik een (sleutel, IV)-paar nooit:
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 };
}
Bij een bestand van 2 GB alloceert file.arrayBuffer() de volledige buffer in het geheugen, wat mobile Safari regelmatig laat crashen. Splits het bestand in stukken van 4 MB, versleutel elk stuk met een unieke IV afgeleid van een teller met een willekeurig prefix, en voeg een versiebyte en salt toe zodat de decryptor begrijpt wat hij ontvangt.
Grote bestanden streamen via TransformStream
Om geheugenoverloop te vermijden, verpakt u de versleuteling in een TransformStream en leidt u het bestand erdoorheen:
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() retourneert een ReadableStream<Uint8Array> die lui van schijf leest. Het piekgeheugengebruik blijft onder de 20 MB, zelfs bij een upload van 10 GB.
De symmetrische sleutel verpakken met RSA-OAEP
Wanneer u een bestand met een specifieke ontvanger wilt delen, genereert u eenmalig een RSA-sleutelpaar en publiceert u de openbare sleutel:
const keypair = await crypto.subtle.generateKey(
{ name: 'RSA-OAEP', modulusLength: 4096,
publicExponent: new Uint8Array([1,0,1]), hash: 'SHA-256' },
true, ['wrapKey', 'unwrapKey']
);
Genereer een AES-GCM-sleutel voor het bestand en verpak hem:
const wrapped = await crypto.subtle.wrapKey(
'raw', fileKey, keypair.publicKey,
{ name: 'RSA-OAEP' }
);
4096-bit RSA-sleutels bieden circa 150-bit beveiliging tot 2030 conform NIST SP 800-57. Voor forward secrecy of post-quantum-weerstand combineert u RSA-OAEP met ECDH over P-384, of migreert u naar ML-KEM (Kyber) zodra de WebCrypto-werkgroep dat ondersteunt.
Sleutels veilig bewaren in IndexedDB
CryptoKey-objecten zijn standaard niet-extraheerbaar, waardoor u ze in IndexedDB kunt opslaan zonder de ruwe bytes ooit bloot te stellen aan JavaScript:
const db = await openDB('keystore', 1);
await db.put('keys', keypair.privateKey, 'user-signing-key');
Browsers serialiseren de sleutel via het structured-clone-algoritme en bewaren de feitelijke bytes in de crypto-backend. Een gecompromitteerd script kan encrypt of decrypt aanroepen met de opgeslagen sleutel, maar kan de sleuteldata niet uitlezen — een stuk beter dan base64-sleutels in localStorage bewaren.
Foutafhandeling van de API
SubtleCrypto gooit een OperationError bij mislukte authenticatie tijdens ontsleuteling — dit betekent gewoonlijk dat de ciphertekst is gemanipuleerd, de IV onjuist is of de gebruiker het verkeerde wachtwoord heeft ingevoerd. DataError wijst op een invoerbuffer met de verkeerde lengte, NotSupportedError op een niet-geïmplementeerd algoritme, en InvalidAccessError op een sleutel zonder de juiste gebruiksmarkeringen. Verpak ontsleuteling altijd in try/catch en toon een neutrale melding zoals "bestand kon niet worden ontsleuteld" — verklap niet of de tag of de structuur heeft gefaald.
Bekende valkuilen
Firefox op Android blokkeert de UI-thread voor enkele seconden bij deriveKey-iteraties boven circa 1 miljoen — voer sleutelafleiding uit in een aparte Worker. Safari onder 16.4 ondersteunt crypto.subtle.verify met PSS-padding niet. Chrome beperkt getRandomValues-aanroepen tot 64 KB per aanroep, dus gebruik een lus voor meer entropie. En ArrayBuffer-overdrachten via postMessage zijn zero-copy maar maken het origineel onbruikbaar, wat mensen regelmatig verrast.
HexaTransfer gebruikt precies deze AES-256-GCM- plus PBKDF2-pipeline voor elke upload, met sleutels afgeleid in een Worker en ciphertekst gestreamd naar opslag zonder dat de server ooit plaintekst ziet. Probeer het op https://hexatransfer.com — gratis, geen account vereist, maximaal 10 GB.
Alles samenbrengen
Een minimale versleutelde uploadflow: genereer salt en IV met getRandomValues, leid een AES-256-GCM-sleutel af uit het wachtwoord van de gebruiker via PBKDF2, stream het bestand door een TransformStream die elk stuk van 4 MB versleutelt, voeg een kleine header toe met versie, salt en aantal stukken, en stuur het resultaat naar uw server. Keer bij het downloaden dit proces stuk voor stuk om en onderschep OperationError als signaal voor een verkeerd wachtwoord of corruptie. De Web Crypto API biedt alles wat u nodig hebt, en de native browserimplementatie — conform RFC 8446 voor de onderliggende TLS 1.3-verbinding — overtreft elke JavaScript-alternatief met een orde van grootte.
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