Kapitel 3 · Übung
Passwörter sicher speichern
Passwörter aus schnellen Hashes zurückgewinnen
Befund F1 ist noch offen: Die Leseliste speichert jedes Passwort als Klartext. Kapitel 2 hat gesagt, dass sich ein Hash nicht rückgängig machen lässt. Hier ist also eine verlockende Behebung: hashlib.sha256(password) speichern und nie das Passwort selbst. Ein Dieb, der den Zustand kopiert, findet Digests, und Digests lassen sich nicht zurückverwandeln.
Diese Lektion spielt den Dieb gegen genau diesen Entwurf, und der Entwurf verliert.
Zuerst ein Wort zum Projekt dieses Kapitels. Dein Server hat eine Änderung, die noch nichts Neues tut: Die Entscheidung über eine Anmeldung steht jetzt in einer eigenen login_user(state, credentials), die handle() aufruft. In reading_server.py gibt es zwei neue Konstanten, ANA_RECORD und BEN_RECORD, die auf Lektion 6 warten. Und es gibt eine neue Datei, passwords.py, in der du den größten Teil des Kapitels arbeitest.
Das Datenleck
Das Projekt enthält eine Datei, die für ein Datenleck steht: leaked_table.json. Sie stammt von einer erfundenen Buchclub-Website, die für jeden Benutzer einen
cat leaked_table.json
Acht Benutzer, acht Digests aus 64 Hex-Zeichen und kein Passwort in Sicht. Der Dieb hier ist der dritte Angreifer aus Kapitel 1. Er hat die Datei und sendet keine Anfragen, also bremst ihn nichts auf dem Server. Er arbeitet auf seinem eigenen Rechner, so lange er will.
Raten und prüfen
Ein Digest lässt sich nicht zurückverwandeln, und das muss er auch nicht. Der Dieb kann einen Versuch hashen und vergleichen. Wenn hashlib.sha256(b"letmein").hexdigest() gleich einem Digest in der Tabelle ist, ist das Passwort dieses Benutzers letmein.
Alles, was der Dieb braucht, sind gute Versuche, und Menschen sind vorhersagbar. words.txt enthält 300 Versuche von der Art, wie Menschen sie wählen: ein Wort, manchmal gefolgt von einer Jahreszahl, 123 oder !. Die Datei beginnt mit reading, reading1, reading12. Sie wurde für diesen Kurs nach einer einfachen Regel geschrieben: zwanzig Grundwörter mit je fünfzehn Endungen.
Ein Durchgang über die Wörter
Hier ist die Idee, die diesen Angriff billig macht. Hashe jedes Wort einmal und behalte die Ergebnisse in einem
Die 300 Digests werden ein einziges Mal berechnet und dienen allen acht Benutzern. Sie würden genauso gut acht Millionen Benutzern dienen. Die Arbeit des Diebs wächst mit der Anzahl der Versuche und nicht mit der Anzahl der Konten.
Die Tabelle hat 8 Benutzer, und die Liste hat 300 Wörter. Wie viele SHA-256-Digests muss der Dieb berechnen, um jedes Wort gegen jeden Benutzer auszuprobieren?
Deine Aufgabe
Schreib in passwords.py die Funktion crack(table, words).
tableist ein Dictionary vom Benutzernamen zum SHA-256-Hex-Digest des Passworts dieses Benutzers.wordsist einevon , die ausprobiert werden sollen. Hashe jedes Wort als
hashlib.sha256(word.encode("utf-8")).hexdigest().Gib ein Dictionary vom Benutzernamen zum Passwort zurück, für jeden Benutzer, dessen Digest zu einem Wort passt. Lass die Benutzer weg, die du nicht zurückgewonnen hast.
Verwende nur die beiden main() unten in der Datei liest leaked_table.json und words.txt und ruft deine Funktion auf. Führ sie dann aus:
python passwords.py
Lies das Ergebnis
amir bookworm123
bea coffee!
chidi letmein
dana sunshine2024
eli (not recovered)
farah dragon99
gus sunshine2024
hana (not recovered)
6 of 8 passwords recovered from 300 words
Sechs von acht Konten, aus 300 Versuchen, in der Zeit, die ein Druck auf die Eingabetaste dauert.
Sieh dir dana und gus an. Sie haben dasselbe Passwort, und das hättest du wissen können, bevor du irgendetwas geknackt hast: Ihre Digests in der Tabelle sind gleich. Dieselbe Eingabe ergibt immer denselben Digest. Das hat einen Digest zu einem guten
eli und hana haben überlebt. Ihre Passwörter stehen nicht in der Liste, und das ist der einzige Grund. Der Hash hat sie nicht geschützt. Eine längere Liste könnte sie finden.
SHA-256 ist nicht gebrochen, und es ist weiterhin das richtige Werkzeug für ein ETag. Für ein Passwort ist es aus einem Grund das falsche Werkzeug: Es wurde gebaut, um schnell zu sein. Eine einzige Grafikkarte kann jede Sekunde eine enorme Anzahl von SHA-256-Versuchen ausprobieren, und eine echte Wortliste hat viele Millionen Einträge, nicht 300.
dana und gus haben in der Tabelle denselben Digest. Was weiß ein Dieb, bevor er irgendetwas rät?
Einen einfachen Digest zu speichern schließt F1 also nicht. Der Dieb dieser Lektion hatte zwei Vorteile: Ein Versuch diente jedem Benutzer, und jeder Versuch kostete fast nichts. Die nächste Lektion nimmt ihm beide.
Die Regel dieses Kurses gilt für das, was du heute geschrieben hast, genauso wie für alles andere. Diese Tabelle wurde für den Kurs erfunden. Führ crack nie gegen Daten aus, die nicht dir gehören.
Klicke auf Check my work, wenn python passwords.py sechs Passwörter ausgibt.
Aufgabe
Schreib in passwords.py die crack(table, words).
tableist einvom Benutzernamen zum -Hex-Digest des Passworts dieses Benutzers. wordsist einevon , die ausprobiert werden sollen. Gib ein Dictionary vom Benutzernamen zum Passwort zurück, für jeden Benutzer, dessen Digest zu einem Wort passt, gehasht als
hashlib.sha256(word.encode("utf-8")).hexdigest().Lass Benutzer weg, die du nicht zurückgewonnen hast.
Verwende nur die beiden
. main()liest die Dateien für dich.
Führ im python passwords.py aus. Es sollte mit 6 of 8 passwords recovered from 300 words enden.
Klicke dann auf Check my work. Es ruft crack() mit einer Tabelle und einer Wortliste auf, die du noch nicht gesehen hast: fünf Benutzer und sechs Wörter, wobei sich zwei Benutzer ein Passwort teilen, ein Passwort nicht unter den Wörtern ist und eines Buchstaben außerhalb von ASCII hat. Das Ergebnis muss genau die zurückgewonnenen Benutzer enthalten, jeden mit seinem Passwort, und die übergebene Tabelle muss unverändert bleiben. Ohne Wörter oder ohne Benutzer muss das Ergebnis ein leeres Dictionary sein. Die zwei Dateien in deinem Projekt werden nicht verwendet, und nichts aus deinem Terminal wird bewertet.