Passwörter sicher speichern · Übung
Ein Passwort mit scrypt hashen
Die langsame hashlib.scrypt. Sie zu benutzen ist ein Aufruf. Der Aufruf nimmt sechs
Der Aufruf
hashlib.scrypt(password_bytes, salt=salt, n=n, r=r, p=p, dklen=32)
Das erste Argument ist das Passwort als
bytes. Ein Passwort kommt als Text an, also kodiere es:password.encode("utf-8").saltist das, als bytes.n,rundplegen die Kosten fest. Der nächste Abschnitt erklärt sie.dklenist die Anzahl der Bytes, die du zurückhaben willst. Der Standardwert ist 64. Dieser Kurs verlangt 32, damit jeder gespeicherte Hash dieselbe Länge hat.
Alles nach dem Passwort muss über den Namen übergeben werden. Die Funktion ist so definiert, wie die Keyword-only-TypeError: scrypt() takes exactly 1 positional argument (5 given) aus.
Was tut hashlib.scrypt(b"x", salt, 16, 8, 1)?
Was n, r und p kosten
n ist der Arbeitsfaktor. Er muss eine Zweierpotenz sein. Eine Verdopplung von n verdoppelt die Zeit, die ein Aufruf braucht, und den Speicher, den er benötigt. Dieser zweite Teil unterscheidet scrypt von einer Funktion, die nur langsam ist: Speicher lässt sich nur teuer vervielfachen, also kann ein Dieb nicht billig eine riesige Zahl von Versuchen nebeneinander ausführen.
r skaliert die Größe der Speicherblöcke. Der Speicher, den ein Aufruf braucht, beträgt etwa 128 * r * n Bytes. Mit r=8 und n=2**14 sind das 128 * 8 * 16384 Bytes, also 16
p wiederholt die Arbeit. Dieser Kurs lässt es bei 1.
Ein Übungswert
Dieser Kurs verwendet n=2**14, r=8, p=1. passwords.py hält die drei Zahlen als COST_N, COST_R und COST_P. Sie wurden gewählt, weil die Sandbox klein ist und eine Prüfung dich nicht warten lassen soll. Sie sind ein Übungswert, und du solltest wissen, wie sie sich mit einem echten vergleichen.
Das OWASP Password Storage Cheat Sheet ist eine weit verbreitete Referenz für diese Einstellungen. Sein Standard-Minimum für scrypt in einem echten Dienst ist N=2**17, r=8, p=1. Vergleiche die ganze Einstellung und nicht n allein: Bei gleichem r und p ist das n dieses Kurses ein Achtel davon, also leistet ein Aufruf ein Achtel der Arbeit, an Zeit und an Speicher. Das Cheat Sheet führt auch Einstellungen auf, die es als gleichwertig ansieht, mit niedrigerem N und höherem p. Eine davon ist N=2**14, r=8, p=5. 2**14 kommt dort also vor, mit dem Fünffachen des p dieses Kurses. Wo sie verfügbar ist, bevorzugt das Cheat Sheet eine neuere Funktion, Argon2id, die Pythons Standardbibliothek nicht hat.
Wenn du hier ein höheres n ausprobierst, stößt du zuerst an eine Wand. Bei n=2**15 löst der Aufruf einen memory limit exceeded endet. scrypt braucht bei dieser Einstellung etwas mehr als 32 MiB, und 32 MiB ist die Standardgrenze. Das Argument maxmem hebt die Grenze an, und ein echter Dienst setzt es.
Warum heißt die Einstellung dieses Kurses, n=2**14, r=8, p=1, ein Übungswert?
Kosten als Parameter
hash_password(password, salt, *, n, r, p) nimmt die Kosten von seinem Aufrufer und legt intern nichts fest. Das bringt zwei Dinge. Ein Test kann winzige Kosten wie n=16 übergeben und läuft im Nu. Und ein Dienst kann seine Kosten nächstes Jahr erhöhen, indem er drei Konstanten ändert, ohne diese Funktion anzufassen.
Schreib den Aufruf als hashlib.scrypt(...), über das
Zwei Tatsachen über die Eingabe, der Ehrlichkeit halber. scrypt akzeptiert ein Passwort jeder Länge, sogar ein leeres, also begrenzt hier nichts, was ein Benutzer wählen darf. Und zwei Passwörter, die auf dem Bildschirm gleich aussehen, können verschiedene Bytes sein, weil sich manche Zeichen auf mehr als eine Weise schreiben lassen. Kapitel 7 kommt darauf zurück. Nichts in dieser Lektion hängt davon ab.
Deine Aufgabe
Schreib in passwords.py zwei Funktionen:
new_salt()gibtsecrets.token_bytes(SALT_BYTES)zurück: 16 zufällige Bytes.hash_password(password, salt, *, n, r, p)gibthashlib.scrypt(...)der-Bytes des Passworts zurück, mit dem übergebenen Salt, den übergebenen n,rundpunddklen=HASH_BYTES. Gib die Bytes so zurück, wie sie sind.
Probier es dann im
python -c "import passwords; print(passwords.hash_password('correct horse', passwords.new_salt(), n=2**14, r=8, p=1).hex())"
Jeder Lauf gibt 64 Hex-Zeichen aus, und die beiden Läufe geben verschiedene aus. Das Passwort war beide Male dasselbe. Das Salt war neu.
Das ist richtig, und es ist auch ein Problem. Um anas Passwort nächste Woche zu prüfen, muss der Server dieselben Bytes noch einmal berechnen, und dafür braucht er dasselbe Salt und dieselben n, r und p. Im Moment sind sie weg, sobald die Funktion zurückkehrt. Die nächste Lektion gibt ihnen einen Ort, an dem sie leben können.
Klicke auf Check my work, wenn beide Funktionen geschrieben sind.
Aufgabe
Schreib in passwords.py zwei
new_salt()gibtsecrets.token_bytes(SALT_BYTES)zurück.hash_password(password, salt, *, n, r, p)gibthashlib.scrypt(...)der-Bytes des Passworts zurück, mit dem übergebenen , den übergebenen n,rundpunddklen=HASH_BYTES. Leg keine Kosten innerhalb der Funktion fest und gib die Bytes unverändert zurück.
Schreib beide Aufrufe über ihre secrets.token_bytes(...) und hashlib.scrypt(...).
Klicke dann auf Check my work. Es ruft new_salt() mit einem Ersatz für secrets.token_bytes auf, der die verlangte Größe festhält, und prüft, dass sich zwei echte Salts unterscheiden. Es ruft hash_password() viermal mit Passwörtern, einem Salt und Kosten auf, die du noch nicht gesehen hast; einer der vier Aufrufe ist bei n=2**14. Jedes Mal zählt es die Aufrufe von hashlib.scrypt: Es muss genau einer sein, mit dem Salt und den n, r und p, die übergeben wurden, und das Ergebnis müssen die 32 Bytes sein, die scrypt für die UTF-8-Bytes des Passworts liefert. maxmem zusätzlich zu übergeben ist in Ordnung. Nichts wird gemessen, und nichts aus deinem