Neue Passwort-Speicherung in Spring Security 5

Was hat sich geändert?

Wer bislang zur Speicherung der Passwörter in Spring Security eine Digest-basierte Speicherung nutzte (z.B. ShaPasswordEncoder oder MessageDigestPasswordEncoder), wird bei der Migration auf Spring Security 5 über das neue Speicherformat für Passwörter stolpern:

{id}encodedPassword

Die id gibt dabei den zu verwendenden PasswordEncoder an. Beispielsweise ist {bcrypt} mit dem BCryptPasswordEncoder kodiert und {noop} wäre ein Passwort im Klartext.

Eine schöne historische Übersicht und die Begründung warum die SHA basierten Kodierungen inzwischen unsicher sind, bietet die Dokumentation.

Digest-basierte Verfahren sind unsicher

Bislang galt die "Verschlüsselung" der Benutzer-Passwörter mit SHA-256 und einem zufälligen "Salt" als der Stand der Dinge. Der SHA Algorithmus soll schnell einen Hash ohne Kollision erstellen. Wegen der schnellen Operation ermöglicht dies aber auch Brute-Force-Attacken. Ein Algorithmus der diese Operation aufwändiger macht, ist daher zur Speicherung von Passwörtern besser geeignet. bcrypt wurde zu diesem Zweck entwickelt.

Migration SHA-256

Wer bislang den ShaPasswordEncoder nutzte, braucht bei der Migration lediglich ein Prefix vor das gespeicherte Passwort setzen, und den verwendenten Hash-Algorithmus angeben:

UPDATE "user" SET password = CONCAT('{SHA-256}', password);

Der DelegatingPasswordEncoder nutzt dieses Prefix und leitet das kodierte Passwort an den MessageDigestEncoder weiter.

Migration SHA-256 mit Salt

Eine übliche Kombination ist die Kodierung mit dem ShaPasswordEncoder und ein Salt, der mit dem Benutzer in der Datenbank gespeichert wird. Auf diesen greift der Encoder über die ReflectionSaltSource zu. Der neue MessageDigestPasswordEncoder ist in der Lage den Salt zu nutzen.

Dazu muss der Salt in geschweiften Klammern vor das Passwort gesetzt werden:

String s = salt == null ? null : "{" + salt + "}";
String migratedPassword = s + user.getPassword();

Damit wird das Passwort gemeinsam mit dem Salt nun im Passwort-Feld des Benutzers gespeichert. Um jetzt zusätzlich den eingesetzten Hash-Algorithmus austauschbar zu halten, muss auch noch die Information über den eingesetzten Hash-Algorithmus mit in dem Attribut gespeichert werden.

Beispiel:

String password = "thePassword";
String salt = "theSalt";
String migratedPassword = "{SHA-256}{theSalt}thePassword";

Eine Datenbankmigration könnte so aussehen. Die Spalten in der Tabelle user sollen password bzw. salt sein.

UPDATE "user" SET password = CONCAT('{SHA-256}{', salt, '}', password);

Der DelegatingPasswordEncoder wertet {SHA-256} aus und delegiert an den MessageDigestPasswordEncoder("SHA-256") weiter. Dieser wertet nun den Salt in den geschweiften Klammern aus und kann so das kodierte Passwort dekodieren.

Weitere Informationen

Lesenswerte JavaDocs

Quellen