Ga naar inhoud
HexaTransfer
Terug naar blog
Encryptie & beveiliging

Veilige willekeurige nummergeneratie: basis van sterke crypto

Veilige willekeurige generatie is cruciaal voor encryptie. Hoe crypto.getRandomValues werkt en waarom zwakke willekeur beveiliging breekt.

Veilige willekeurige nummergeneratie is de basis waarop elk ander stuk cryptografie rust. In JavaScript vult crypto.getRandomValues(buffer) een TypedArray met cryptografisch veilige willekeurige bytes uit de cryptografisch veilige pseudo-willekeurige nummergenerator (CSPRNG) van het besturingssysteem (/dev/urandom op Linux/macOS, BCryptGenRandom op Windows, SecRandomCopyBytes op iOS/macOS). Gebruik Math.random() voor niets dat beveiliging raakt — het is een Mulberry32- of xorshift-stijl PRNG ontworpen voor snelheid, niet voor onvoorspelbaarheid, en zijn uitvoer is voorspelbaar na het waarnemen van een paar samples. Een zwakke RNG breekt AES-sleutels, TLS-handshakes, nonce-uniciteit in GCM, token-onraadbaarheid en elk ander beveiligingsprimitief dat afhankelijk is van onvoorspelbare bits.

Het verschil tussen willekeurig en cryptografisch willekeurig

Een PRNG (pseudo-willekeurige nummergenerator) produceert een deterministische stroom vanuit een zaad. Gegeven het zaad en algoritme kun je elke uitvoer reproduceren. Prima voor games, simulaties en Monte Carlo-methoden. Catastrofaal voor cryptografie.

Een CSPRNG (cryptografisch veilige PRNG) is gezaaid vanuit een echte entropiebron (thermische ruis, interrupt-timing, hardware RNG-instructies zoals Intel's RDSEED) en het ontwerp zorgt ervoor dat uitvoer rekenkundig niet te onderscheiden is van echte willekeur, en dat het waarnemen van eerdere uitvoer niet helpt bij het voorspellen van toekomstige uitvoer.

JavaScript geeft je beide. Math.random() is een PRNG. crypto.getRandomValues() is een omhulsel over de CSPRNG van het besturingssysteem. Één regel code verschil, enorm beveiligingsverschil.

Het canonieke correcte gebruik

// Genereer 256-bits AES-sleutel aan willekeurige bytes
const keyBytes = crypto.getRandomValues(new Uint8Array(32));

// Genereer een 96-bits GCM nonce
const nonce = crypto.getRandomValues(new Uint8Array(12));

// Genereer een 128-bits salt
const salt = crypto.getRandomValues(new Uint8Array(16));

// Genereer een URL-veilig willekeurig token
const tokenBytes = crypto.getRandomValues(new Uint8Array(32));
const token = btoa(String.fromCharCode(...tokenBytes))
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

crypto.getRandomValues() is synchroon, vult de buffer in-place en geeft de buffer terug. Maximale verzoekgrootte is 65.536 bytes in één aanroep (een quota opgelegd door de specificatie om blokkering te voorkomen). Voor meer willekeurig materiaal roep je meerdere keren aan.

Node.js equivalenten

const { randomBytes, randomFillSync, webcrypto } = require('crypto');

const keyBytes = randomBytes(32);  // Geeft een Buffer terug
// Of Web Crypto-compatibel
const nonce = webcrypto.getRandomValues(new Uint8Array(12));

Node's randomBytes trekt uit dezelfde onderliggende CSPRNG als Web Crypto. Gebruik welke API-stijl ook past bij je code. Voor isomorfe code die in beide omgevingen draait, komt webcrypto.getRandomValues overeen met de browser.

Waarom Math.random faalt

V8 (Chrome/Node), SpiderMonkey (Firefox) en JavaScriptCore (Safari) implementeren allemaal Math.random() als een snelle PRNG zonder cryptografische garanties. V8 gebruikt een xorshift128+-variant. Onderzoekers hebben aangetoond dat na het waarnemen van ~5 uitvoerwaarden een aanvaller de interne toestand kan herstellen en alle toekomstige uitvoer kan voorspellen. In 2015 hebben Mike Pound en collega's V8's Math.random-toestand omgekeerd in echte bug bounties.

Als je Math.random() gebruikt om sessietokens, wachtwoordherstellinks, versleutelingsnonces of deel-ID's te genereren, kunnen aanvallers die er een paar van waarnemen de rest voorspellen. Dit is niet theoretisch — het is een veelvoorkomende bug-klasse in audits.

Veelvoorkomende misbruiken

Een bibliotheek-PRNG zaaien met Math.random():

// SLECHT
const seed = Math.floor(Math.random() * 2**32);

Niets downstream kan willekeuriger zijn dan het zaad. Gebruik crypto.getRandomValues(new Uint32Array(1))[0] in plaats daarvan.

Date.now() gebruiken als entropie: Tijd is te raden binnen krappe vensters. Zelfs gecombineerd met een kleine willekeurige factor, lekken tijdstempels genoeg bits voor aanvallers.

Je eigen mengsel maken door bronnen te XOR-en: Doe dit niet. Besturingssysteem-CSPRNG's mengen al elke nuttige entropiebron. Je eigen roer toevoegen vermindert entropie typisch eerder dan het te verhogen.

Modulusbias bij het genereren van bereiken: randomBytes[0] % 10 is niet uniform verdeeld over 0-9 omdat 256 geen veelvoud is van 10. Voor uniforme willekeurige gehele getallen in een bereik gebruik je rejection sampling:

function randomInt(max) {
  const range = new Uint32Array(1);
  const threshold = 2**32 - (2**32 % max);
  do {
    crypto.getRandomValues(range);
  } while (range[0] >= threshold);
  return range[0] % max;
}

Entropiebronnen en opstartproblemen

Op Linux is /dev/urandom altijd veilig na vroege boot. Tijdens de eerste paar seconden van boot op systemen zonder hardware RNG kan de kernelpool onvoldoende gevuld zijn. Dit werd uitgebuit in de Debian OpenSSL-bug van 2008 waarbij een patch het entropiemengen verwijderde en alleen het proces-ID als zaad overbleef. Sleutels gegenereerd in dat venster hadden slechts 2^15 mogelijke waarden — in seconden geëtaald.

Moderne systemen zaaien de kernel-CSPRNG vanuit: RDSEED op x86-64 (indien beschikbaar), ARMv8.5-A RNG-instructies, thermische ruis van diverse randapparatuur, interrupt-timing, toetsenbord/muis indien interactief. Op servers met hardware als Intel Ice Lake of AMD Zen 3+ wordt de CSPRNG binnen microseconden na boot gezaaid.

Voor Docker-containers: de /dev/urandom van de host wordt standaard doorgegeven. Geen actie vereist. Voor serverless (AWS Lambda, Cloudflare Workers) verwerkt de runtime entropiezaaiing per aanroep.

Sessietokens en deel-ID's

Voor een bestandsoverdrachtsservice genereer je willekeurige identificatoren voor:

  • Bestand-ID's in URL's (aanvallers mogen geen geldige ID's kunnen raden)
  • Deeltokens voor wachtwoordbeveiligde links
  • CSRF-tokens
  • Versleutelingssleutels (per-bestand AES-sleutels)
  • Nonces voor GCM

Minimumlengte: 128 bits (16 bytes) voor botsing en onraadbaarheid, 256 bits (32 bytes) voor sleutels. URL-veilige codering via base64url voegt ~33% lengte toe; hex voegt 100% toe.

Een 32-byte base64url-gecodeerd token is 43 tekens en vrijwel vrij van botsingen bij 2^256.

Testen op zwakke RNG

Tekenen dat je RNG gebroken of zwak is:

  • Identieke tokens gegenereerd door verschillende verzoeken (botsing in wat een enorme ruimte zou moeten zijn)
  • Uitvoer doorstaat visuele tests maar mislukt dieharder of PractRand statistische batterijen
  • Zaad-hergebruik na processtap — elke deployopstelling gebruikt dezelfde initiële toestand
  • Gegenereerde sleutels vallen in patronen (bijv. eerste 4 bytes variëren maar laatste 28 zijn identiek)

In productie zie je dit waarschijnlijk niet tenzij er iets catastrofaal fout is. De faalwijze is gewoonlijk stilzwijgend: aanvallen worden gewoon praktisch op wat een 2^256-zoekruimte zou moeten zijn.

Audit: elke aanroep van Math.random() in een codebase moet worden beoordeeld. Een grep op Math.random over je bronboom is een goede wekelijkse hygiënecontrole. Elk beveiligingsrelevant aanroeppunt omzetten naar crypto.getRandomValues kost minuten en voorkomt echte kwetsbaarheden.

Willekeurige strings en UUID's

Voor mensgerichte identificatoren geeft crypto.randomUUID() een v4 UUID terug (122 bits willekeur) in een standaardformaat:

const id = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"

Ondersteund in Chrome 92+, Firefox 95+, Safari 15.4+, Node 14.17+. Goed voor database primary keys, API-verzoek-ID's en niet-beveiligingskritische unieke identificatoren. Gebruik expliciete getRandomValues voor alles dat aangepaste formaten of hogere entropie vereist.

Op servers: vermijd aangepaste RNG-pools

Sommige serverframeworks bieden hun eigen willekeurige pools die beweren de systeem-CSPRNG te "mengen" met applicatieniveau-entropie. Behandel dit met argwaan. Aangepaste menging verbetert de uitvoer van de kernel zelden en kan entropie stilzwijgend verminderen als er bugs in zitten.

Als je op Node of een grote runtime zit, is de ingebouwde crypto.randomBytes correct en snel. Vervang het niet door mixers van derden.

De conclusie

Elk stuk cryptografie in een bestandsoverdrachtsapp is afhankelijk van onvoorspelbare willekeurige bytes. AES-sleutels, GCM-nonces, PBKDF2-salts, deeltokens, anti-CSRF-tokens, sessie-ID's — allemaal hebben ze hetzelfde primitief nodig: crypto.getRandomValues() in browsers, crypto.randomBytes() of webcrypto.getRandomValues in Node. Gebruik die. Nooit Math.random(). Nooit tijdstempels. Nooit je eigen mengsel.

HexaTransfer leidt elke per-bestand AES-sleutel, nonce en URL-identifier af van crypto.getRandomValues() op de client. Server-side deel-ID's komen van crypto.randomBytes. Één API, consistent gedrag, geen manier om per ongeluk voorspelbare bits in het systeem te laten lekken.

Het primitief is saai precies omdat het dat moet zijn. Saai, correct en overal beschikbaar — precies wat cryptofundamenten zouden moeten zijn.

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