Hilfe, ich habe unsichere Passwörter in meiner Datenbank!
Seit einigen Monaten sollte jedem, der die IT-Nachrichten und sogar die Mainstream-Presse etwas verfolgt, klargeworden sein dass es keine besonders kluge Idee ist, Passwörter von Kunden im Klartext zu speichern. Hashing ist nach wie vor eine gute Idee, wenn man den richtigen Algorithmus verwendet. Hashes sollten nur noch eingesetzt werden wenn man einen Salt mit einbaut, aber auch dan gibt es noch große Unterschiede zwischen alten Verfahren wie MD5/SHA und neueren Algorithmen wie bcrypt. Dank Artikeln zum Thema bcrypt von Oliver sollte auch jedem mindestens eine Lösung bekannt sein die man heutzutage einsetzen sollte. Doch wie macht man das in der Praxis, wie migriert man die bestehenden Daten zum neuen bcrypt Verfahren? Darauf möchte ich hier etwas eingehen.
Ein neues Projekt
Wenn ein neues Projekt gestartet und auf der grünen Wiese begonnen wird ist alles sehr einfach. Neue Kunden- bzw. Benutzerpasswörter werden einfach mittels bcrypt gehasht und dann in einer Datenbank gespeichert. Man kann natürlich auch Textdateien, Arbeitsspeicher oder etwas anderes nehmen, Hauptsache man hat das gehashte Passwort zur Hand wenn sich der Benutzer einloggen möchte und kann das eingegebene Passwort gegen das Gespeicherte prüfen.
Passwörter aus einem bestehenden System nutzen
Etwas komplizierter wird es wenn zwar ein neues Projekt begonnen wird, aber Daten aus einem alten Projekt importieren werden müssen. Wenn das alte Projekt Klartextkennwörter enthält (dann wird es höchste Zeit!), ist das Problem schnell gelöst: Wir nutzen wiederum bcrypt und speichern sie in der neuen Datenbank, genauso wie wir es für neue Benutzer tun würden. Fertig.
Sollten die Passwörter jedoch im alten System bereits in gehashter (MD5/SHA1) Form vorliegen haben wir ein Problem: Wir können daraus aus bekannten Gründen nicht das Klartextkennwort berechnen, um dieses dann mittels bcrypt neu ablegen zu können. Was wir aber tun können ist folgendes: Wir nehmen das bereits gehashte Passwort (angenommen es sei SHA1), und wenden dann einfach bcrypt auf den bestehenden Hash an. In der Datenbank gespeichert ist dann nicht
bcrypt(klartextpasswort, bcrypt_salt)
sondern
bcrypt(sha1(klartextpasswort), bcrypt_salt)
Das funktioniert super und ist kein (Sicherheits-)Problem, wir müssen dann nur für Neukunden genau das selbe tun: Bei der Registrierung erstellen wir erst den SHA1-Hash und dann bcrypten wir es. Natürlich dürfen wir nicht vergessen, beim Login die selbe Prozedur zu verwenden wenn es darum geht das Passwort zu prüfen.
Sollten userspezifische Salts im alten Hash im Einsatz sein wird es etwas komplizierter. Dann sieht das ganze ungefähr so aus:
sha1_salt + bcrypt(SHA1(sha1_salt + klartextpasswort), bcrypt_salt)
Parallelbetrieb
Zum Upgrade eines bestehenden Passwortbestands gibt es noch eine weitere Möglichkeit: Wir legen neben dem aktuellen Datenbankfeld (beispielsweise SHA1Password) ein weiteres Feld an für das neue sichere bcrypt-Passwort: BcryptPassword.
Falls sich nun ein Benutzer einloggen möchte und das neue Datenbankfeld noch leer ist, prüfen wir sein Passwort auf die alte Weise. Sollte es richtig sein hashen wir das Klartextpasswort mittels bcrypt und füllen das entsprechende Feld BcryptPassword. Im gleichen Atemzug löschen wir den alten, unsicheren Datenbankeintrag. So hat man im BcryptPasswort Feld Passworte der Form
bcrypt(klartextpasswort, bcrypt_salt)
und die alten unsicheren Einträge verschwinden mit der Zeit.
Wenn man dann nach einigen Monaten noch einige Benutzer hat die sich nie eingeloggt haben, und deshalb dort das alte Datenbankfeld noch gefüllt ist, kann man sich überlegen ob man diesen Benutzern mal eine E-Mail sendet sodass die sich einmal einloggen, oder man löscht das alte unsichere Passwort und der Benutzer muss beim nächsten Login mittels der Passwort vergessen Funktion oder einem Telefonanruf ein neues Passwort setzen. Oder aber man lebt damit dass bei einigen noch das alte Passwort in der Datenbank steht, die haben dann einfach Pech gehabt.
Diese Methode kann man übrigens auch mit nur einem Datenbankfeld machen wenn man möchte. Wenn das Passwortfeld beispielsweise mit $2a$ anfängt, dann kann es nicht das SHA1 Passwort sein sondern ist bereits das bcrypt Passwort. Dann kommt man unter Zuhilfenahme einer kleinen Fallunterscheidung auch mit einem Feld aus.
Beide Verfahren kombinieren
Wer die Vorteile aus beidem haben möchte kann auch folgendermaßen vorgehen: Es werden auf einen Schlag alle alten SHA1 Passwörter mittels bcrypt verarbeitet und gespeichert. Damit ist man schonmal die alte unsichere Weise los. Sobald sich dann jemand einloggt wird dieses verschachtelte Verfahren abgelöst durch die nichtverschachtelte Variante. Wir kombinieren also die beiden oben genannten Methoden.
Warum bcrypt besser ist als MD5 oder SHA
Die Gründe hat Oliver in seinem Artikel bereits dargelegt, hier habe ich noch eine interessante Tabelle vom Projekt scrypt, das soll der letzte Schrei sein und noch besser geeignet sein als bcrypt, ich muss es mir noch im Detail anschauen. Es ist ähnlich schnell wie bcrypt, nutzt aber mehr Speicher, und deshalb wird teurere Hardware benötigt.
Fazit
Vorteil der verschachtelten Methode ist dass alle Passwörter direkt auf einmal ohne Login der Benutzer umgestellt werden können. Wenn die alten Hashes bereits Salts enthalten wird es evtl. etwas aufwändiger weil man den alten Hash nicht verlieren darf.
Wenn man auf die Logins der Benutzer warten kann hat man danach keine Verschachtelung und etwas weniger Altlasten mitzuschleppen.
Welcher Weg auch gewählt wird, die Zeit ist auf jeden Fall gut investiert, denn auch Passwortspeicherverfahren müssen ab und zu an den Stand der Technik angepasst werden. Je größer die Firma oder das Projekt ist, an dem ihr arbeitet, umso wichtiger ist es und umso mehr wird man es euch danken, sollte doch mal etwas passieren.
Zu scrypt kann ich sagen, dass ich das Prinzip zwar für zukunftsweisend halte, aber die Umsetzung mir nicht gefällt. Es gibt insgesamt drei Faktoren. CPU/Memory und Parallel Kostenfaktoren. Das macht es recht schwierig, nur den Speicher und die CPU Zeit zu planen, da mit dem Speicher auch die CPU Zeit steigt.
Im Webbereich, wo man mit einer maximalen Größe Speicher arbeiten muss, ist das ungünstig. Ausserdem gibt es noch keine richtig brauchbare Umsetzung für PHP, ausser https://github.com/DomBlack/php-scrypt wo man aber selbst kompilieren muss.
Oliver
2 Okt 12 at 14:48
Bei einem bestehenden Projekt bin ich während es lief noch auf bcrypt umgestiegen. Dazu habe ich in der DB lediglich die Feldlänge erweitert und beim Login geprüft, ob das Feld aus der DB 32 Stellen lang ist (MD5). Wenn ja, dann habe ich die MD5-Hashes verglichen, falls nicht, dann die bcrypt Hashes. Wenn das Passwort korrekt war und in der DB als MD5 stand, hab ich es einfach überbügelt. Beim nächsten Mal sind wird dann automatisch mit bcrypt getestet.
Dieser Trick funktioniert nur, wenn die Hashes eine signifikant unterschiedliche Länge haben.
Chris
2 Okt 12 at 17:50
Ich trampe seit ein paar Moanten durch Usergroups, Stammtische und Barcamps und erzähle über unsichere Passwörter.
http://www.ikonoshirt.de/stuff/
Der Beitrag ist super, danke dafür!
Eine kleine Ergänzung hätte ich noch: PBKDF2. Password-Based-Key-Derivation-Function Version 2 ist ein Standard, der definiert, wie wir Passwörter speichern können. Nach Aussage eines Kollegen sind sowohl bcrypt, scrypt als auch PBKDF2 momentan empfohlene Methoden um Passwörter sicher zu speichern, er merkte nur an, dass PBKDF2 akademisch stärker erforscht sei, womit das Risiko geringer ist, dass früher oder später eine „Lücke“ gefunden wird, die ds Knacken der Passwörter doch wieder furchtbar einfach macht.
Implementiert ist er z.B. hier:
(bald) http://www.php.net/manual/de/function.hash-pbkdf2.php
http://www.php.net/manual/de/function.hash-hmac.php#109260
oder in der PHP-CryptLib von Anthony Ferrara inkl. Testabdeckung:
https://github.com/ircmaxell/PHP-CryptLib/
Fabian Blechschmidt
2 Okt 12 at 17:50
[…] Hilfe, ich habe unsichere Passwörter in meiner Datenbank! […]
Linkhub – Woche 40-2012
8 Okt 12 at 17:27