Minder random dan het lijkt

Elke programmeertaal heeft wel een functie om een random getal te genereren. Maar de uitkomst van zo’n functie is minder random dan je misschien zou verwachten. Sterker nog; de uitkomst is zelfs goed voorspelbaar. Dat betekent dat als je random-functies gebruikt voor bijvoorbeeld sessie-cookies of andere security-gerelateerde zaken je daar rekening mee zou moeten houden.

Het probleem zit hem er in dat een computer (of eigenlijk een CPU) geen functie kent voor een compleet willekeurig getal. Dus wordt er gebruik gemaakt van ‘pseudo-random’ functies (die term zul je ook tegen komen in de handleiding van random-functies in elke programmeertaal). Met pseudo-random wordt bedoeld dat het op het oog willekeurig lijkt, maar het in werkelijkheid niet is. Zulke functies maken gebruik van een ‘teller’ (ook wel ‘seed’ genoemd) op basis waarvan het volgende getal wordt berekend. Dat betekent dat als je 2x achter elkaar een random getal opvraagt bij dezelfde ‘seed’ je ook 2x het zelfde ‘willekeurige’ getal krijgt.

Het volgende script laat dat zien:

<?php
srand(12345);

for ($i = 0; $i < 10; $i++) {
    echo rand(0, 9);
}

echo " ";

srand(12345);

for ($i = 0; $i < 10; $i++) {
    echo rand(0, 9);
}

De output hiervan is (hoe vaak je het ook draait):

1312002032 1312002032

Waarom lijkt een random-functie bij normaal gebruik dan toch random? Dat komt omdat de meeste random-implementaties automatisch een ‘seed’ instellen zodat de kans dat je 2x een aanroep met dezelfde seed doet niet zo heel groot is. Vaak wordt de huidige tijd (in milliseconden) gebruikt als seed.

Als je sessie-ids (of andere ‘random’ strings) genereert met een standaard random-functie werkt dat dus beperkend in het aantal unieke strings dat je zou kunnen genereren, ongeacht de lengte van je unieke string.

Bijvoorbeeld: als je strings van 10 tekens samenstelt uit de reeks A t/m Z heb je theoretisch 26 ^ 10 unieke strings (141.167.095.653.376 mogelijkheden). Maar als een seed een 32-bits integer is, zijn er dus maar 2 ^ 32 unieke strings (4.294.967.296) als uitkomst mogelijk.

Is dat erg? Dat ligt er aan. Als van de buitenkant niet zichtbaar (of te achterhalen) is op welke manier de sessie-ids worden gegenereerd, zal een kwaadwillende alsnog alle combinaties moeten proberen om er eentje te kunnen overnemen. Maar als bekend is op welke manier de strings tot stand komen kun je (offline, dus met veel rekenkracht) een tabel aanleggen met alle mogelijke combinaties met elke mogelijke input-seed. En als je vervolgens de site bezoekt en je krijgt een bepaalde sessie-id toegewezen kun je in die tabel opzoeken welke sessie-sleutels er ‘in de buurt’ liggen op basis van de seed (die afhangt van het tijdstip). En dan wordt de kans op een gestolen sessie-sleutel ineens een stuk groter.

Een oplossing zit hem er in door voor security-gerelateerde zaken andere random-functies te gebruiken. Deze kun je vinden onder de noemer ‘cryptographically secure pseudo-random number generators’ voor bijvoorbeeld PHP (CSPRNG) of Java (SecureRandom).