Ga naar inhoud
HexaTransfer
Terug naar blog
Encryptie & beveiliging

WebAssembly encryptie: bijna native cryptosnelheid in de browser

Gebruik WebAssembly voor bijna native encryptiesnelheden. Vergelijk WASM crypto-implementaties.

WASM-gecompileerde cryptobibliotheken draaien op ruwweg 60-80% van native C-snelheid in de browser, wat 3-10x sneller is dan pure JavaScript-implementaties. Voor versleutelingsprimitieven die ontbreken in de Web Crypto API (ChaCha20-Poly1305, Argon2id, XChaCha20, post-kwantum-KEM's zoals Kyber) is WASM het praktische pad naar bruikbare prestaties. libsodium.js is de dominante optie en biedt de volledige libsodium-API met een WASM-binary van 200 KB. Voor Web Crypto-ondersteunde primitieven zoals AES-256-GCM wint de native hardwareversnelde implementatie van de browser ruimschoots van WASM, dus WASM is niet altijd het juiste gereedschap. Deze gids behandelt wanneer je het moet inzetten en hoe je de afwegingen benchmarkt.

Wanneer native Web Crypto wint

Voor algoritmen die Web Crypto al blootstelt — AES-GCM, AES-CBC, AES-CTR, PBKDF2, HMAC, RSA-OAEP, ECDH, ECDSA, SHA-256/384/512 — gebruikt de native implementatie van de browser hardwareversnelling via AES-NI op x86 en ARMv8-crypto-extensies op mobiel. Typische AES-256-GCM doorvoer:

  • Web Crypto (Chrome op Apple Silicon): 1,7 GB/s
  • Web Crypto (Chrome op Intel x86 met AES-NI): 1,2 GB/s
  • libsodium WASM AES-GCM: 400-800 MB/s
  • Native OpenSSL referentie: 3-5 GB/s

WASM heeft geen toegang tot AES-NI vanuit de sandbox; het valt terug op bitgesneden AES-implementaties die langzamer maar constant-time zijn. Gebruik voor alles wat Web Crypto ondersteunt eerst de browser-API.

Waar WASM wint

Voor algoritmen die ontbreken in Web Crypto:

  • ChaCha20-Poly1305: sneller dan AES op apparaten zonder AES-NI. Geen native browserondersteuning in 2026.
  • XChaCha20-Poly1305: 192-bits nonces maken willekeurige-nonce gebruik veilig op elke schaal.
  • Argon2id: de PHC-winnende wachtwoord-hashingfunctie. Geen native browserondersteuning.
  • X25519 / Ed25519: Safari 17 en Firefox 129 voegden native ondersteuning toe, maar WASM is nog steeds het draagbare pad.
  • BLAKE2b / BLAKE3: snelle hashfuncties die niet in Web Crypto zitten.
  • Kyber, Dilithium, SPHINCS+: post-kwantumprimitieven; uitsluitend bibliotheekterrein.
  • Streaming AEAD: libsodium's crypto_secretstream verwerkt streaming versleuteling van grote bestanden netjes; Web Crypto heeft geen equivalent.

Voor deze algoritmen bestaan JavaScript-implementaties maar zijn 5-20x langzamer dan WASM. Op een bestand van 1 GB met ChaCha20-Poly1305 draait WASM in ~2 seconden terwijl pure JS 15-40 seconden duurt.

libsodium.js: het werkpaard

libsodium-wrappers (de Emscripten-gebouwde libsodium met JS-omhulsels) is de go-to bibliotheek. 200 KB WASM + ~50 KB JS-omhulsel, gzipped.

import _sodium from 'libsodium-wrappers';
await _sodium.ready;
const sodium = _sodium;

// ChaCha20-Poly1305 geauthenticeerde versleuteling
const key = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
  plaintext, null, null, nonce, key
);

De "sumo"-build (bevat meer algoritmen) is ~600 KB; de standaardbuild dekt 90% van gebruiksscenario's inclusief AEAD, wachtwoord-hashing (Argon2id), X25519 en Ed25519.

Laad dynamisch zodat de WASM-binary de initiële paginaweergave niet blokkeert:

async function getSodium() {
  if (!window._sodium) {
    const mod = await import('libsodium-wrappers');
    await mod.ready;
    window._sodium = mod;
  }
  return window._sodium;
}

Streaming AEAD via crypto_secretstream

Voor grote bestandsoverdracht is libsodium's crypto_secretstream_xchacha20poly1305 de overzichtelijkste streaming AEAD in de browser:

const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
const chunk1 = sodium.crypto_secretstream_xchacha20poly1305_push(
  state, plaintext1, null,
  sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);
// Laatste chunk gebruikt TAG_FINAL zodat ontvanger afkapping detecteert
const chunkLast = sodium.crypto_secretstream_xchacha20poly1305_push(
  state, plaintextLast, null,
  sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);

De TAG_FINAL markering laat de ontvanger afkapingsaanvallen detecteren — als een aanvaller afsluitende chunks weggooit, mislukt ontsleuteling op de client. Web Crypto's AES-GCM heeft deze eigenschap niet; je zou afkappingsdetectie zelf moeten bouwen (chunk-aantal in AAD, of een algemene bestandshash die na ontsleuteling wordt geverifieerd).

Argon2 in de browser

Voor wachtwoordgebaseerde sleutelafleiding in de browser is Argon2id via WASM de beste praktijk voor 2026. Opties:

  • argon2-browser: toegewijde Argon2 WASM-bibliotheek, ~200 KB
  • libsodium.js: Argon2id via crypto_pwhash, als je libsodium al gebruikt
  • @noble/hashes: pure-JS Argon2id, ~15 KB bundel, 3-5x langzamer dan WASM

Voorbeeld met argon2-browser:

import argon2 from 'argon2-browser';
const result = await argon2.hash({
  pass: password,
  salt: salt, // Uint8Array, 16+ bytes
  type: argon2.ArgonType.Argon2id,
  time: 3,
  mem: 65536, // KiB, dus 64 MiB
  parallelism: 4,
  hashLen: 32,
});
// result.hash is een Uint8Array die je kunt gebruiken als AES-256-sleutel

Kalibreer parameters op je doelgebruikerswachttijd. OWASP 2024 baseline (m=19 MiB, t=2, p=1) draait in ~300-500 ms. Sterker (m=64 MiB, t=3, p=4) draait in ~1-2 seconden op moderne hardware.

Post-kwantumcrypto via WASM

Kyber (sleutelinkapseling) en Dilithium (handtekeningen) werden in 2024 door NIST gestandaardiseerd als ML-KEM en ML-DSA. JavaScript-implementaties bestaan (pq-crystals heeft een referentie) maar WASM-versies zoals liboqs-js zijn sneller en beter auditeerbaar.

Voor bestandsoverdracht laten post-kwantum-KEM's je een symmetrische bestandssleutel verpakken met een kwantumveilig publieke-sleutel-primitief. Hybride schema's (ML-KEM + X25519) beschermen tegen zowel klassieke als toekomstige kwantumaanvallers. Chrome leverde ML-KEM in TLS-handshakes in versie 116 (2023), maar WASM op applicatieniveau blijft het pad voor bestandsinhoudssleutels.

Bundelgrootte overwegingen

WASM-binaries worden verzonden als onderdeel van je JS-bundel of lui opgehaald. Ruwe groottes (gzipped):

  • libsodium.js standaard: 200 KB
  • libsodium.js sumo: 600 KB
  • argon2-browser: 200 KB
  • liboqs-js (post-kwantum): 1 MB+

Voor een landingspagina die in-browser versleutelt is 200-300 KB WASM verdraaglijk als het lui wordt geladen na gebruikersinteractie. Voor een app die versleuteling als primaire functie heeft, is het verzenden van WASM bij initiële lading redelijk. Gebruik rel="modulepreload" hints of service worker caching om latere laadtijden direct te houden.

Controleer het netwerkpaneel in browser-devtools om te bevestigen dat de WASM-binary in de cache wordt opgeslagen na de eerste lading. Verkeerd geconfigureerde cache-headers kunnen bij elk bezoek opnieuw downloaden veroorzaken.

Compilatie- en instantiatie-overhead

WebAssembly.instantiate() parseert en compileert de binary, wat 20-100 ms duurt voor een module van 200 KB op desktop, 100-500 ms op mobiel. Dit gebeurt eenmaal per sessie. Sla de gecompileerde module op in IndexedDB via WebAssembly.Module-serialisatie voor snellere latere laadtijden.

WebAssembly.instantiateStreaming() pipeline download en compilatie, waardoor 30-50% van starttijd wordt bespaard vergeleken met instantiate() op een opgehaalde respons:

const response = fetch('/sodium.wasm');
const { instance } = await WebAssembly.instantiateStreaming(response, importObject);

Geheugenbeheer valkuilen

WASM-modules wijzen geheugen toe in lineaire pagina's (64 KiB elk). libsodium start standaard met klein geheugen en groeit naar behoefte, maar onbegrensd groeien kan browsergrenzen raken (typisch 4 GB lineair geheugenplafond in 32-bits WASM). Voor versleuteling van bestanden met meerdere GB's:

  • Stroom chunks door de WASM-module, laad niet het volledige bestand in WASM-geheugen
  • Maak buffers vrij na elk chunk via sodium.memzero of door referenties te nullen
  • Monitor WebAssembly.Memory.buffer.byteLength in langlopende sessies

Memory64 (voorgesteld, gedeeltelijk geïmplementeerd in Chrome) verwijdert het 4 GB-plafond. Vooralsnog is gesegmenteerde verwerking het betrouwbare pad.

Beveiligingsoverwegingen specifiek voor WASM

WASM draait in dezelfde origin als de pagina die het heeft geladen, waarbij dezelfde CSP- en CORS-regels van toepassing zijn. Als een aanvaller code injecteert in je origin (XSS), kunnen ze de exports van je WASM-module aanroepen. WASM biedt geen extra beveiligingsgrens.

Constant-time garanties: de meeste cryptobibliotheken gecompileerd naar WASM bewaren constant-time eigenschappen uit de C-bron, maar de WASM-naar-JIT-vertaling kan variabele-time code introduceren op sommige platforms. Auditlogs van libsodium noemen een paar WASM-specifieke constant-time zorgen. Voor de meeste bedreigingsmodellen is dit verwaarloosbaar; voor verdediging tegen lokale aanvallers met precieze timing-metingen overweeg je de leemte.

Het praktische recept

Voor een bestandsoverdrachtsapp in 2026:

  • Gebruik Web Crypto voor AES-256-GCM, PBKDF2, HMAC, SHA-256, RSA-OAEP als je RSA nodig hebt
  • Gebruik libsodium.js via WASM voor Argon2id wachtwoord-hashing
  • Gebruik libsodium.js voor streaming versleuteling van grote bestanden (crypto_secretstream)
  • Gebruik native Ed25519/X25519 waar ondersteund, fall back naar libsodium anders
  • Laad WASM lui na gebruikersinteractie om de initiële bundel licht te houden
  • Benchmark op doelapparaten; neem geen desktopgetallen als maatstaf voor mobiel

HexaTransfer blijft op Web Crypto AES-GCM voor de bulk bestandsversleuteling omdat het hardwareversnelde pad het snelst is. WASM is gereserveerd voor algoritmen die native API's niet kunnen leveren.

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