Wachtwoorden: inloggen met verify
Hier bouw je op verder: Try en except
Weet je dit niet meer zeker? Lees het terug voordat je verdergaat.
- Python Try en except
Na les 7 staan er alleen nog Argon2-hashes in je database. Bij
het inloggen kun je niet meer vergelijken met ==: de hash van hetzelfde
wachtwoord is elke keer anders. Daarvoor is ph.verify. Die haalt het zout en
de instellingen uit de opgeslagen hash, rekent de hash van het ingetypte
wachtwoord uit, en vergelijkt.
/inloggen met ph.verify
from argon2.exceptions import VerifyMismatchError
NEP = ph.hash("niemand")
@app.post("/inloggen")
async def inloggen(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
opgeslagen = db.get(naam, NEP)
try:
ph.verify(opgeslagen, wachtwoord)
except VerifyMismatchError:
return {"bericht": "Naam of wachtwoord klopt niet"}
if opgeslagen == NEP:
return {"bericht": "Naam of wachtwoord klopt niet"}
return {"bericht": "Ingelogd"}
- Regel 1:
VerifyMismatchErroris de fout dieph.verifygooit als het wachtwoord niet klopt. - Regel 3:
Een nep-hash: de Argon2-hash van een wachtwoord dat bij geen enkel account hoort. Je rekent hem één keer uit, als de server start.
- Regel 8:
Bestaat de naam niet, dan geeft
db.getde nep-hash terug in plaats vanNone. - Regel 9-12:
Klopt het wachtwoord, dan gaat
ph.verifyzonder fout verder. Klopt het niet, dan vang je de fout af en meld je dat inloggen niet lukt. - Regel 13-14:
Was het de nep-hash, dan bestaat de naam niet. Ook wie toevallig
niemandintypt, komt dus niet binnen. De melding is dezelfde als bij een fout wachtwoord, zodat je niet verraadt welke namen wél een account hebben.
Zet de import bij je andere imports, NEP direct onder ph = PasswordHasher(),
en vervang je oude /inloggen door deze, in hetzelfde main.py als je
/registreer uit les 7. import hashlib mag weg: niets gebruikt hem meer.
Waarom die nep-hash? ph.verify rekent een Argon2-hash uit, en die is expres
traag, zoals je in les 6 mat. Zou je bij een onbekende naam meteen
antwoorden, dan komt dat antwoord vele malen sneller terug dan bij een bestaande naam met een fout wachtwoord.
De melding is gelijk, maar de tijd verraadt dan alsnog welke namen een account
hebben. Met de nep-hash doet de server bij een onbekende naam hetzelfde
rekenwerk, en duren beide antwoorden even lang.
Waar de controle zit
Registreren en inloggen naast elkaar: de server maakt en controleert de hash, de browser ziet hem nooit.
Browser: Je registreert. Naam en wachtwoord gaan via het formulier naar de server.
Hier zit de controleServer: ph.hash maakt de hash. Met een eigen zout, en expres traag. Alleen de hash gaat de database in. · Registreren met Argon2
Browser: Je logt in. Weer naam en wachtwoord via het formulier.
Hier zit de controleServer: ph.verify vergelijkt. Met het zout en de instellingen uit de opgeslagen hash. · Inloggen met verify
Browser: Ingelogd, of één melding. Een foute naam en een fout wachtwoord geven hetzelfde antwoord.
Doe de test
Draai inloggen.py uit les 4 opnieuw. Het doet drie
pogingen: Sara met haar wachtwoord, Sara met een letter ernaast, en Bob, die
geen account heeft.
Zo ziet je main.py er nu uit
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
from fastapi import FastAPI, Form, HTTPException
from sqlitedict import SqliteDict
app = FastAPI()
ph = PasswordHasher()
NEP = ph.hash("niemand")
@app.post("/registreer")
async def registreer(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
if naam in db:
raise HTTPException(status_code=400, detail="Deze naam is al bezet")
db[naam] = ph.hash(wachtwoord)
db.commit()
return {"bericht": f"Welkom, {naam}"}
@app.post("/inloggen")
async def inloggen(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
opgeslagen = db.get(naam, NEP)
try:
ph.verify(opgeslagen, wachtwoord)
except VerifyMismatchError:
return {"bericht": "Naam of wachtwoord klopt niet"}
if opgeslagen == NEP:
return {"bericht": "Naam of wachtwoord klopt niet"}
return {"bericht": "Ingelogd"}
Registreren en inloggen werken nu allebei met Argon2.
Er gaat iets mis
Zelf vergelijken
Je vergelijkt zelf, in plaats van ph.verify te gebruiken:
# FOUT
if ph.hash(wachtwoord) == opgeslagen:
return {"bericht": "Ingelogd"}
# GOED
try:
ph.verify(opgeslagen, wachtwoord)
except VerifyMismatchError:
return {"bericht": "Naam of wachtwoord klopt niet"}
return {"bericht": "Ingelogd"}
Er komt geen foutmelding, maar niemand kan meer inloggen, ook niet met het goede wachtwoord.
Oorzaak: ph.hash maakt elke keer een nieuw zout, dus elke keer een andere
hash. Twee hashes van hetzelfde wachtwoord zijn nooit gelijk.
Oplossing: vergelijk nooit zelf. ph.verify haalt het zout uit de
opgeslagen hash en rekent daarmee.
Een oude hash in de database
Je draait inloggen.py, en het script stopt met een lange foutmelding die zo
eindigt:
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
In de terminal van je server staat bij dezelfde poging
"POST /inloggen HTTP/1.1" 500, en daaronder een
foutmelding die eindigt met argon2.exceptions.InvalidHashError.
Oorzaak: in de database staat nog een sha256-hash of een wachtwoord als
gewone tekst, uit de lessen hiervoor. ph.verify herkent die code niet als
Argon2-hash en gooit een fout, en de server stuurt dan een 500 met als inhoud
de platte tekst Internal Server Error. Daar zit geen JSON in, dus
antwoord.json() in je script loopt vast.
Oplossing: gooi gebruikers.db weg, en draai registreer.py opnieuw met
je server van nu.
Opdrachten
Opdracht 1: Predict - Wat zie je?
Noor heeft welkom123. Je stuurt een inlogpoging met naam noor en wachtwoord
Welkom123, met een hoofdletter.
Vraag: wat antwoordt de server?
Antwoord
Naam of wachtwoord klopt niet. Een hoofdletter is een ander teken, dus een
ander wachtwoord, en ph.verify gooit een VerifyMismatchError.
Opdracht 2: Investigate - Wachten in plaats van rekenen
Een klasgenoot stelt voor om de nep-hash weg te laten, en bij een onbekende
naam de server even te laten wachten met time.sleep(0.05). Dan duurt dat
antwoord toch ook lang. Waarom is de nep-hash beter?
Tip
Hoe lang duurt één Argon2-hash? Kijk naar je meting in les 6, en vergelijk die met de meting van een klasgenoot.
Antwoord
Hoe lang ph.verify duurt, verschilt per computer en per moment: in les 6
kreeg elke computer andere getallen, en een server die het druk heeft, rekent
langzamer. Een vaste 0.05 past dus bijna nooit precies, en wie de tijd meet,
ziet het verschil alsnog. Worden de instellingen van Argon2 later strenger, dan
klopt het getal helemaal niet meer.
Met de nep-hash doet de server bij een onbekende naam precies hetzelfde rekenwerk als bij een bestaande naam. Daardoor duren beide antwoorden even lang, op elke computer.
Door naar les 9: wachtwoord wijzigen.