+49 (0) 9491 / 742 988 50

Eine lokale Version der Haveibeenpwned Passwort Datenbank erstellen (mit Python und SQLite)

In diesem Beitrag zeigen wir, wie man eine lokale Version der Haveibeenpwned Datenbank erstellen kann. Diese kann dann genutzt werden, um Passwörter auf ihre Sicherheit zu überprüfen, ohne dass dazu eine Internetverbindung nötig wäre.

Was ist Haveibeenpwned?

Haveibeenpwned ist eine Webseite des Sicherheitsforschers Troy Hunt, in der geleakte Credentials aus Data Breaches gesammelt werden. Als User kann man seine Emailadresse eingeben und erfährt dann, ob diese bereits in einem Data Breach enthalten ist. Außerdem kann man auch sein Passwort auf dieselbe Weise testen.

Ist ein Passwort in einem Breach enthalten, sollte es sofort geändert werden.

Welchen Sinn hat eine lokale Version?

Troy Hunt ist ein weltweit anerkannter Sicherheitsforscher und auch die Webseite ist hervorragend abgesichert. Dennoch mag vielleicht für manche die Vorstellung, ihr Passwort einfach so auf dieser Seite einzugeben, seltsam klingen. Eventuell wird eine solche Prüfung auch durch bestimmte Unternehmens Richtlinien erschwert oder unmöglich gemacht.

Als Alternative gibt es übrigens auch noch eine Haveibeenpwned API. Bei dieser hasht man sein Passwort auf dem eigenen Computer mit SHA-1 und überträgt dann lediglich die ersten 5 Zeichen des Hashes an die API. Diese antwortet dann mit einer Liste aller Hashes, die mit diesen 5 Zeichen beginnen. Im Durchschnitt erhält man dabei 400 Antworten. Mit diesen kann man nun auf dem eigenen Computer prüfen, ob einer davon dem ursprünglichen Passwort Hash entspricht.

Eigentlich also eine schöne Lösung, aber falls ein Angreifer es schafft, einen Man in the Middle Angriff durchzuführen, könnte er möglicherweise den an die API übergebenen Passwort Hash auslesen. Anschließend könnte er versuchen, diesen mittels Rainbow Tables zu knacken.

Bei einer lokalen Version werden dagegen keine Passwörter oder Hashes über das Netzwerk übertragen, alles spielt sich rein auf Ihrem PC ab.

Was benötigt man?

Im Grunde benötigt man drei Dinge:

  • Eine gute Internetverbindung: Aktuell ist das kleinste File (NTLM Format, geordnet nach Hash) 8,5GB groß. Das größte File (SHA-1 Format, geordnet nach Häufigkeit) sogar 12,5GB. Der Download kann also eine Weile dauern.
  • Speicherplatz: Das entzippte Text File ist etwa 20GB groß. Wenn wir die Passwörter direkt in diesem Text File suchen würden, würde das extrem lange dauern. Um schnelle Abfragen zu gewährleisten, brauchen wir eine Datenbank. Um diese noch weiter zu optimieren, verwenden wir Indizes. Dadurch steigt aber auch die Größe der Datenbank deutlich an, sodass man mit etwa 50GB an freiem Speicher planen sollte.
  • Python: Ich habe Python 3.9 verwendet, es sollte allerdings auch mit anderen 3.X Versionen funktionieren. Das Schöne daran ist, dass in der Python Installation bereits SQLite enthalten ist, sodass wir keine zusätzliche Datenbank installieren müssen.

Schritt 1: Text File herunterladen

Auf der Seite https://haveibeenpwned.com/Passwords können die Passwort Hashes heruntergeladen werden. Dabei stehen SHA-1 und NTLM Versionen zur Verfügung, die entweder nach Hash oder nach Häufigkeit des Vorkommens geordnet sind.

Da es für uns keine Rolle spielt, ob wir mit SHA-1 oder NTLM hashen und auch keine besondere Ordnung der Hashs benötigen, können wir uns für die kleinste Version (NTLM, ordered by Hash) entscheiden, um die Downloadzeit zu minimieren.

Heruntergeladen wird ein ZIP File, das wir nach dem erfolgreichen Download unzippen. Heraus kommt ein Einfaches, aber sehr großes, Text File. Bitte versuchen Sie nicht, dieses File mit dem normalen Texteditor zu öffnen. Im besten Fall passiert dabei einfach nichts, im schlimmsten Fall hängt sich der PC auf, weil er diese Größe nicht verarbeiten kann. Wenn Sie das File öffnen möchten, um sich den Inhalt genauer anzuschauen, benötigen Sie einen speziellen Editor wie den EmEditor.

Das Textfile enthält in jeder Zeile einen Hash eines Passworts und die Häufigkeit mit der dieses Passwort in Breaches aufgetaucht ist. Getrennt sind Hash und Häufigkeit durch einen Doppelpunkt. Das sieht dann so aus:

Der Hash 00000001F4A473ED6959F04464F91BB5 kommt also 4-mal in der Datenbank vor.

Schritt 2: Datenbank erstellen

Wir könnten an dieser Stelle auch einfach das Textfile benutzen, um zu überprüfen, ob unser Passwort darin enthalten ist. Dazu würden wir unser Passwort hashen und dann das File Zeile für Zeile durchgehen. Jede Zeile müssten wir zunächst am Doppelpunkt splitten und dann den Hash mit unserem Passwort Hash vergleichen. Das ist machbar, dauert aber sehr lange (auf meinem Laptop etwa 20 Minuten).

Da das dann doch etwas unpraktisch wäre, erstellen wir im nächsten Schritt eine Datenbank, um Abfragen schneller vornehmen zu können. Dazu verwenden wir SQLite, das bereits in der Standard Python Installation enthalten ist, wir benötigen also weder zusätzliche Datenbank Software, noch zusätzliche Python Libraries.

Wenn wir eine klassische SQLite Datenbank erstellen, sind die Abfragen allerdings immer noch nicht besonders schnell. Das ist auch nicht weiter verwunderlich, immerhin sind 613 Millionen Passwort Hashes in der Datenbank enthalten.

Um die Performance der Abfragen zu erhöhen, müssen wir einen Index für die Spalte Hash anlegen. Indizes bewirken, dass die Datenbank als B-Baum aufgebaut wird. Diese Datenstruktur reduziert die Zeit für eine Suche im Baum massiv, hat allerdings den Nachteil, dass die Datenbank deutlich größer wird. Man sollte etwa mit dem dreifachen an Speicherplatz rechnen, den das ursprüngliche Text File benötigt.

Der Code, um die Datenbank zu erstellen, sieht dann so aus:

(Wie üblich finden Sie den Code natürlich auch auf unserem GitHub Account)

Zunächst erstellen wir mit sqlite3.connect eine SQLite Datenbank, hier mit dem Titel „pwned_indexed“. Diese Datenbank wird im selben Verzeichnis erzeugt, in dem auch das Skript liegt. Wer die Datenbank an einer anderen Stelle speichern möchte, kann stattdessen auch einen Pfad angeben.

Anschließend erzeugen wir eine Tabelle „passwords“ mit den beiden Spalten Hash und Prevalence (Häufigkeit). Für die Spalte Hash erzeugen wir zudem einen Index, sodass die Suche nach Hashwerten später besonders schnell abläuft.

Dann lesen wir die Werte aus dem Text File in die Datenbank ein: Dazu wird jede Zeile gelesen, am Doppelpunkt gesplittet und der erste Wert (Index 0) als Hash und der zweite Wert (Index 1) als Häufigkeit gespeichert.

Zusätzlich geben wir nach einer Million eingetragener Hashwerte einen Zwischenstand auf dem Terminal aus. Dabei wird einfach geprüft, ob die Count Variable ohne Rest durch eine Million teilbar ist. Ist das der Fall, wird der Fortschritt ausgegeben.

Ganz am Ende müssen wir nur noch alles commiten und die Verbindung zur Datenbank wieder schließen. Das alles sind gerademal 30 Zeilen Python Code (wer auf die Ausgabe des Fortschritts verzichten kann, kann die dafür nötigen vier Zeilen auch noch löschen).

Im Windows Explorer sieht unsere neue SQLite Datenbank dann so aus:

Schaut man sich die Eigenschaften an, sieht man, dass die Datenbank 52.5GB groß ist.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.

Schritt 3: Datenbank abfragen

Nachdem wir nun eine Datenbank mit 613 Millionen Passwort Hashes haben, können wir ein weiteres Skript schreiben, um diese Datenbank abzufragen.

Über die input() Funktion haben wir die Möglichkeit, User Eingaben vorzunehmen. Der User kann dann ein beliebiges Passwort eingeben und auf Breaches prüfen. Das eingegebene Passwort wird mit NTLM gehasht, da dieses Format auch für die Hashes in unserer Datenbank verwendet wurde.

Da die Hashes in der Datenbank alle in Großbuchstaben gespeichert sind, wandeln wir die Eingabe ebenfalls in Großbuchstaben um und decodieren das Bytes Objekt anschließend zu einem String, damit Vergleiche mit den Strings in der Datenbank möglich sind.

Anschließend wird die Verbindung zur Datenbank hergestellt und geprüft, ob der Hash enthalten ist. Ist das der Fall (d.h. es wird ein Resultat zurückgeliefert) wird „Pwned“ und der Hash ausgegeben. Wer mag, könnte an dieser Stelle auch noch die Häufigkeit ausgeben lassen, aber da das für uns keine Rolle spielt (egal ob ein Passwort einmal oder fünfzigtausend Mal gebreacht wurde, man sollte es nicht mehr verwenden), verzichten wir an dieser Stelle darauf.

Auch dieses Skript lässt sich in 24 Zeilen realisieren. Mit insgesamt also 50 Zeilen (wenn man auf die Ausgabe des Fortschritts verzichtet) kann man sich die Funktionalität von Haveibeenpwned nachbauen.

Im Screenshot ist die Ausgabe des Skripts für „password“ zu sehen. Wie zu erwarten, wurde dieses Password bereits gepwned.

Ein völlig zufälliges Passwort ist dagegen noch nicht in der Datenbank enthalten.

Die Abfragen liefern übrigens praktisch sofort ein Ergebnis, da durch die B-Baum Struktur nur sehr wenige Knoten überprüft werden müssen, bis der richtige Hash gefunden wurde.

Was kann man damit machen?

Die lokale Version der Passwort Datenbank kann man benutzen, um einerseits alle vorhandenen Passwörter auf ihre Sicherheit zu überprüfen und andererseits auch um bei neuen Passwörtern zu prüfen, ob diese eventuell schon Teil eines Breachs sind und deshalb nicht verwendet werden sollten.

Alternativ kann man die Datenbank auch in eigene Anwendungen integrieren. Dieses Vorgehen ist von Troy Hunt explizit erlaubt worden und auch mit keinen Kosten verbunden.

Wird die Datenbank in andere Anwendungen integriert, kann dadurch bei der Registrierung von Usern überprüft werden, ob deren Passwörter bereits in einem Data Breach enthalten sind. Trifft das zu, kann das Passwort als unsicher markiert werden und der User kann aufgefordert werden, ein anderes Passwort zu wählen.