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.