Ga naar hoofdinhoud

Wachtwoorden: zout

In les 4 hadden Sara en Noor dezelfde hash, omdat ze hetzelfde wachtwoord kozen. De oplossing is een zout (Engels: salt): een stukje willekeurige tekst dat je vóór het wachtwoord zet voordat je hasht. Elke gebruiker krijgt een eigen zout. Probeer het in een scriptje zout.py:

import hashlib

wachtwoord = "welkom123"

zout_sara = "a1b2c3"
zout_noor = "x9y8z7"

print(hashlib.sha256((zout_sara + wachtwoord).encode()).hexdigest())
print(hashlib.sha256((zout_noor + wachtwoord).encode()).hexdigest())

In de database zie je zo niet meer dat Sara en Noor hetzelfde kozen.

Het zout is geen geheim​

Om bij het inloggen te controleren, moet je de hash opnieuw uitrekenen, en daarvoor heb je hetzelfde zout nodig. Het zout staat daarom naast de hash in de database. Dat is geen probleem: het zout hoeft niet geheim te zijn, alleen per gebruiker anders.

Wel maakt het zout vooraf uitgerekende lijsten onbruikbaar. Er bestaan grote lijsten met de sha256 van miljoenen bekende wachtwoorden, die rainbow tables heten. Zonder zout zoek je een hash daarin op, zoals je een woord opzoekt in een woordenboek. Met een zout per gebruiker moet je zo'n lijst voor elk zout opnieuw maken, en dat is niet te doen.

Een echt zout​

"a1b2c3" is een voorbeeld om te laten zien wat er gebeurt. Een echt zout is lang en echt willekeurig. Dat hoef je niet zelf te regelen: in les 7 gebruik je Argon2, dat bij elke hash vanzelf een nieuw zout maakt en het in de hash zelf bewaart.

Controlevraag
Een site neemt als zout de naam van de gebruiker: sara voor Sara, noor voor Noor. Ze kozen allebei welkom123. Zie je in de gelekte database dat hun wachtwoorden gelijk zijn?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Er gaat iets mis​

can only concatenate str (not "bytes") to str​

TypeError: can only concatenate str (not "bytes") to str

Oorzaak: je vergat de haakjes om zout_sara + wachtwoord. Dan zet .encode() alleen het wachtwoord om in bytes, en Python kan tekst en bytes niet aan elkaar plakken.

Oplossing: plak eerst de tekst aan elkaar, en maak daarna van het geheel bytes:

# FOUT
hashlib.sha256(zout_sara + wachtwoord.encode()).hexdigest()

# GOED
hashlib.sha256((zout_sara + wachtwoord).encode()).hexdigest()

Opdrachten​

Opdracht 1: Run - Met en zonder zout​

Bereken de sha256 van welkom123 zonder zout, en daarna met het zout "a1b2c3" ervoor. Vergelijk ze. Waarom helpt dat tegen een rainbow table met de hashes van veelgebruikte wachtwoorden?

Tip

De hash zonder zout ken je uit les 3.

Antwoord
import hashlib

print(hashlib.sha256("welkom123".encode()).hexdigest())
print(hashlib.sha256(("a1b2c3" + "welkom123").encode()).hexdigest())

Uitvoer:

2a5c5f2623024ce3de6fe7dc8f5e13ca55b7aadc13174254b40af574e37018c1
02c84638cc526f509a1e50b8de1c12a120a1f9f3a441521c81d767942596b42f

Een rainbow table bevat de eerste code, want die hoort bij welkom123. De tweede staat er niet in: die hoort bij a1b2c3welkom123, en dat wachtwoord kiest niemand. Zoek je de gezouten hash op in de lijst, dan vind je niets.

Opdracht 2: Investigate - Rekenen voor duizend gebruikers​

Een aanvaller heeft een gelekte database met duizend gebruikers, en wil weten wie welkom123 als wachtwoord heeft. Hoeveel hashes moet hij daarvoor uitrekenen als de database geen zout gebruikt, zoals in les 4? En hoeveel als elke gebruiker een eigen zout heeft?

Tip

In les 4 vond je met één sha256 van welkom123 zowel Sara als Noor. Lukt dat nog als elk zout een andere hash geeft?

Antwoord

Zonder zout één: hij rekent de hash van welkom123 één keer uit en zoekt die code in de hele database. Iedereen met die code heeft welkom123.

Met een eigen zout per gebruiker duizend: voor elke gebruiker moet hij zijn zout plus welkom123 apart hashen, want elk zout geeft een andere code. Dat het zout in de database staat, helpt hem dus niet: hij heeft duizend keer zoveel werk. Waarom dat telt, zie je in les 6.

Door naar les 6: traag is goed.