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.
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.