Passwörter speichern mit PHP (ger)

2017-08-09

PHP hat einen schlechten Ruf bei Programmierern. Es gibt immer wieder Blog-Einträge und Kommentare, wie schlecht PHP doch sei. Ich sehe es eher so, dass PHP gute und schlechte Seiten hat. Daher habe ich mich entschieden, auch mal den einen oder anderen Blog-Eintrag zu schreiben, der die guten Seiten von PHP beleuchtet. Eine dieser guten Seiten sind die mit PHP 5.5 eingeführten folgenden Funktionen.

  • password_hash
  • password_verify
  • password_needs_rehash

Warum ist das relevant?

In fast allen Webapplikationen müssen Passwörter gespeichert werden. Hierbei müssen die Passwörter für den Fall einer Sicherheitslücke in der Webapplikation oder eines Leaks so gespeichert werden, dass sie nicht durch Dritte oder Mitarbeiter ausgelesen werden können.

Die gängige Methode um Passwörter zu schützen ist hierbei, einen Hash für das Passwort zu errechnen und nur diesen Hash in der Datenbank zu speichern. Allerdings werden hierbei immer wieder grundlegende Fehler gemacht:

  • Falscher oder veralteter Algorithmus zum Erzeugen des Hashes
  • Kein Salt oder Pepper verwendet bzw. falsch verwendet
  • Kein Neuschreiben der alten Passwörter

Falscher oder veralteter Algorithmus zum Erzeugen des Hashes

Die gängigsten Algorithmen zum erzeugen eines Hashes, die fast jeder Programmierer kennt sind MD5, SHA1 und SHA2. Obwohl diese Algorithmen sehr oft zum Sichern von Passwörtern verwendet werden, sind sie eigentlich nicht dafür geeignet.

Dies liegt daran, dass die genannten Algorithmen darauf ausgelegt sind, den Hash schnell zu berechnen. Dies vereinfacht es Angreifern, die Passwörter durch Brute-Force zu berechnen. Stattdessen sollte man auf z.B. bcrypt oder scrypt setzen. Der Vorteil dieser Algorithmen ist, dass man die Kosten für die Berechnung des Hashes über einen Parameter steuern kann. Dadurch sind solche Algorithmen auch für zukünftige Hardware geeignet. Ich möchte hier nicht die Details zu dem Für und Wider der verschiedenen Algorithmen aufzeigen. Wer hierzu tiefergehendes Interesse hat, findet im Internet genügen Blog-Einträge zu dem Thema.

Glücklicherweise ist die Wahl des richtigen Algorithmus etwas, um das man sich in PHP keine Gedanken mehr machen muss. Mit der neu eingeführten Konstante PASSWORD_DEFAULT stellt PHP den aktuell von den PHP-Core-Entwicklern empfohlenen Algorithmus bereit. Das heißt, wenn heute noch der Standardalgorithmus (PASSWORD_DEFAULT) bcrypt ist, kann dies bei der nächsten PHP-Version ein anderer sein.

Die ideale Variante um unter PHP einen Hash zu erzeugen sieht also wie folgt aus:

$passwordHash = password_hash($password, PASSWORD_DEFAULT);

Die Variable $passwordHash beinhaltet jetzt den Hash des Passwortes sowie den Salt und kann so direkt in die Datenbank geschrieben werden.

Kein Salt oder Pepper verwendet bzw. falsch verwendet

Um gehashte Passwörter zu knacken, verwenden Angreifer sogenannte Rainbow-Tables (Siehe:https://de.wikipedia.org/wiki/Rainbow_Table) oder Passwort-Listen.

Durch Passwortlisten oder Rainbow-Tables muss ein Angreifer das Passwort nicht mehr durch Brute-Force errechnen, sondern kann das entsprechende Passwort schnell heraus finden. Um dies zu verhindern, kann man einen sogenannten Salt oder Pepper verwenden.

Im Prinzip sind Salt und Pepper das Gleiche. Der Unterschied liegt darin, dass ein Salt direkt beim Passwort gespeichert wird und ein Pepper an einer anderen Stelle. Ein Salt oder Pepper sind ein zusätzlicher Wert, der einem Passwort hinzugefügt wird, bevor der Hash berechnet wird. Dadurch wird die Verwendung von Rainbow-Tables oder Passwortlisten verhindert. Durch den zusätzlich hinzugefügten Wert sind Rainbow-Tables nicht mehr nutzbar, da die vorberechneten Hashes für gängige Passwörter nicht mehr übereinstimmen. Hierbei ist es egal ob ein Salt oder Pepper verwendet wurde. Auch wenn der Angreifer den Salt kennt, kann er keine Rainbow-Table benutzen.

Dass es keine Option ist, keinen Salt oder Pepper einzusetzen, sollte jedem klar sein. Ohne Salt oder Pepper hilft auch der beste Algorithmus nicht. Doch auch beim Implementieren eines Salt oder Peppers können grundlegende Fehler gemacht werden. Zum einen sollte immer ein kryptografisch generierter Zufallswert als Salt oder Pepper verwendet werden. Geeignet ist hierfür z.B. die mit PHP 7.0 eingeführte Funktion random_bytes. Nicht hierfür geeignet sind Datumsfunktionen sowie rand oder urand. Des Weiteren sollt für jedes Passwort ein anderer Salt oder Pepper verwendet wird.

Aber auch hierum muss man sich bei in PHP keine Gedanken machen. PHP nutzt einen geeigneten Zufalls-Generator um den Salt zu erzeugen. Man kann zwar bei der Funktion password_hash einen Salt angeben, ich rate aber davon ab. PHP kümmert sich bereits von sich aus darum, dass ein geeigneter Salt generiert wird. Daher ist dieses Code-Beispiel weiterhin die beste Variante, um einen Passwort-Hash zu erzeugen:

$passwordHash = password_hash($password, PASSWORD_DEFAULT);

Kein Neuschreiben der alten Passwörter

Ein Algorithmus, der heute noch als sicher betrachtet wird, kann in wenigen Jahren bereits als unsicher betrachtet werden. Daher sollte man regelmäßig prüfen, ob die verwendeten Algorithmen noch sicher sind. Wie ich gezeigt habe, wird bei neuen Passwörtern durch die Verwendung der Variable PASSWORD_DEFAULT immer ein aktueller Algorithmus verwendet. Doch wie sieht es mit Passwörtern aus, die schon länger in der Datenbank sind? Teilweise bestehen Kundenaccounts mehrere Dekaden. In solche langen Zeiträumen tut sich meist sehr viel in der Kryptographie und man sollte, wenn möglich, diese Hashes ersetzen. Ziel sollte es daher sein, regelmäßig zu prüfen, ob der erzeugte Hash noch sicher ist, oder ob ein veralteter Algorithmus verwendet wurde. Hierzu hat PHP die Funktion password_needs_rehash hinzugefügt.

Mit der Funktion password_needs_rehash kann man prüfen, ob der Hash eines Passwortes noch aktuell ist. Da zum Erzeugen eines neuen Hashes die Plain-Text-Zugangsdaten des Benutzers benötigt werden, empfiehlt es sich, bei jedem Login eines Benutzers zu prüfen, ob sein Hash noch in Ordnung ist.


function login(string $username, string $password) : void
{
    $user = load_user_by_username($username);
    if(!password_verify($password, $user->passwordHash) {
        throw new PasswordNotMatchingException();
    }
    if(password_needs_rehash($user->passwordHash, PASSWORD_DEFAULT)) {
        $user->passwordHash = password_hash($password, PASSWORD_DEFAULT));
        persist_user($user);
    }
}
                

Zuletzt noch der Hinweis: Es könnte Sinn machen, Passwörter, die lange nicht mehr auf einen aktuellen Algorithmus aktualisiert wurden, zu löschen. Nutzer sollten darüber dann informiert werden und die Möglichkeit haben, ein neues Passwort anzugeben oder den Account durch eine Validierung über E-Mail wieder zu aktivieren. Dadurch kann sichergestellt werden, dass auch ganz sicher keine unsicheren Passwörter in der Datenbank sind.

Fazit

PHP hat mit den drei Funktionen eine wirklich gut funktionierende und sichere API eingebaut, um Passwörter zu speichern. Ich habe mir mal wenige andere Programmiersprachen angeschaut und konnte keine finden, die das ähnlich einfach gelöst hat. Gerne könnt ihr mir Code-Beispiele im Diskussionsbereich posten, wenn ihr hier anderer Meinung seid ;-).

Back to Posts