Wachtwoorden veilig opslaan · oefening
Een wachtwoord hashen met scrypt
De trage hashlib.scrypt. Hem gebruiken is één aanroep. De aanroep neemt zes
De aanroep
hashlib.scrypt(password_bytes, salt=salt, n=n, r=r, p=p, dklen=32)
Het eerste argument is het wachtwoord als
bytes. Een wachtwoord komt binnen als tekst, dus codeer het:password.encode("utf-8").saltis de, als bytes.n,renpbepalen de kosten. Het volgende deel legt ze uit.dklenis hoeveel bytes je terug wilt. De standaardwaarde is 64. Deze cursus vraagt er 32, zodat elke opgeslagen hash één lengte heeft.
Alles na het wachtwoord moet met naam worden meegegeven. De functie is zo gedefinieerd, net als de keyword-only TypeError: scrypt() takes exactly 1 positional argument (5 given).
Wat doet hashlib.scrypt(b"x", salt, 16, 8, 1)?
Wat n, r en p kosten
n is de werkfactor. Hij moet een macht van twee zijn. Als je n verdubbelt, verdubbelen de tijd die één aanroep kost en het geheugen dat hij nodig heeft. Dat tweede deel onderscheidt scrypt van een functie die alleen traag is: geheugen is duur om te vermenigvuldigen, dus een dief kan niet goedkoop een enorm aantal gokken naast elkaar uitvoeren.
r schaalt de grootte van de geheugenblokken. Het geheugen dat één aanroep nodig heeft, is ongeveer 128 * r * n bytes. Met r=8 en n=2**14 is dat 128 * 8 * 16384 bytes, dus 16
p herhaalt het werk. Deze cursus laat hem op 1.
Een lesinstelling
Deze cursus gebruikt n=2**14, r=8, p=1. passwords.py bewaart de drie getallen als COST_N, COST_R en COST_P. Ze zijn gekozen omdat de sandbox klein is en een controle je niet moet laten wachten. Het is een lesinstelling, en je moet weten hoe die zich verhoudt tot een echte.
De OWASP Password Storage Cheat Sheet is een veelgebruikte bron voor deze instellingen. Het standaardminimum voor scrypt in een echte dienst is daar N=2**17, r=8, p=1. Vergelijk de hele instelling en niet alleen n: met dezelfde r en p is de n van deze cursus een achtste daarvan, dus één aanroep doet een achtste van het werk, in tijd en in geheugen. De sheet noemt ook instellingen die hij als gelijkwaardig beschouwt, met een lagere N en een hogere p. Een daarvan is N=2**14, r=8, p=5. 2**14 komt daar dus wel voor, met vijf keer de p van deze cursus. Waar die beschikbaar is, geeft de sheet de voorkeur aan een nieuwere functie, Argon2id, die de standaardbibliotheek van Python niet heeft.
Als je hier een hogere n probeert, loop je eerst tegen een muur. Bij n=2**15 geeft de aanroep een memory limit exceeded. scrypt heeft bij die instelling iets meer dan 32 MiB nodig, en 32 MiB is de standaardlimiet. Het argument maxmem verhoogt de limiet, en een echte dienst zet het.
Waarom heet de instelling van deze cursus, n=2**14, r=8, p=1, een lesinstelling?
Kosten als parameter
hash_password(password, salt, *, n, r, p) neemt de kosten van zijn aanroeper en legt binnenin niets vast. Dat levert twee dingen op. Een test kan heel lage kosten meegeven, zoals n=16, en in een oogwenk draaien. En een dienst kan volgend jaar zijn kosten verhogen door drie constanten te veranderen, zonder deze functie aan te raken.
Schrijf de aanroep als hashlib.scrypt(...), via de
Twee feiten over de invoer, voor de eerlijkheid. scrypt accepteert een wachtwoord van elke lengte, zelfs een leeg wachtwoord, dus niets hier beperkt wat een gebruiker mag kiezen. En twee wachtwoorden die op het scherm hetzelfde lijken, kunnen verschillende bytes zijn, omdat sommige tekens op meer dan één manier geschreven kunnen worden. Hoofdstuk 7 komt daarop terug. Niets in deze les hangt ervan af.
Je opdracht
Schrijf in passwords.py twee functies:
new_salt()geeftsecrets.token_bytes(SALT_BYTES)terug: 16 willekeurige bytes.hash_password(password, salt, *, n, r, p)geefthashlib.scrypt(...)terug van de-bytes van het wachtwoord, met de gegeven salt, de gegeven n,renp, endklen=HASH_BYTES. Geef de bytes terug zoals ze zijn.
Probeer het daarna in de
python -c "import passwords; print(passwords.hash_password('correct horse', passwords.new_salt(), n=2**14, r=8, p=1).hex())"
Elke run drukt 64 hextekens af, en de twee runs drukken verschillende af. Het wachtwoord was beide keren hetzelfde. De salt was nieuw.
Dat klopt, en het is ook een probleem. Om volgende week het wachtwoord van ana te controleren, moet de server dezelfde bytes opnieuw berekenen, en daarvoor heeft hij dezelfde salt en dezelfde n, r en p nodig. Op dit moment zijn die weg zodra de functie teruggeeft. De volgende les geeft ze een plek om te wonen.
Klik op Check my work als beide functies geschreven zijn.
Opdracht
Schrijf in passwords.py twee
new_salt()geeftsecrets.token_bytes(SALT_BYTES)terug.hash_password(password, salt, *, n, r, p)geefthashlib.scrypt(...)terug van de-bytes van het wachtwoord met de gegeven , de gegeven n,renp, endklen=HASH_BYTES. Leg binnen de functie geen kosten vast, en geef de bytes ongewijzigd terug.
Schrijf beide aanroepen via hun secrets.token_bytes(...) en hashlib.scrypt(...).
Klik daarna op Check my work. Die roept new_salt() aan met een vervanger op de plaats van secrets.token_bytes die de grootte vastlegt waar je om vraagt, en controleert dat twee echte salts verschillen. Hij roept hash_password() vier keer aan met wachtwoorden, een salt en kosten die je niet hebt gezien; een van de vier is bij n=2**14. Elke keer telt hij de aanroepen van hashlib.scrypt: er moet er precies één zijn, met de salt en de n, r en p die werden meegegeven, en het resultaat moet de 32 bytes zijn die scrypt geeft voor de UTF-8-bytes van het wachtwoord. Ook maxmem meegeven is prima. Er wordt niets getimed, en niets uit je