Durch eine interessante Diskussion im CodeIgniter Forum, auf die ich durch Elliot Haughin’s Blog gestoßen bin (wo Elliot sich in Sachen Passwortdatensicherung zur gleichen Methode wie ich bekennt), habe ich mal wieder (dieses Thema lässt einen nie ganz los, wenn man täglich WebApps schreib darüber nachgedacht, wie man Passwörter am sinnvollsten mithilfe von PHP und MySQL speichert.
Ganz egal ob ihr eine große oder kleine App schreibt, die Passwörter in sicherer Form zu speichern ist immer sinnvoll. Gehen wir einmal davon aus, dass es einem Hacker tatsächlich gelungen sein sollte, an die Daten unserer Nutzertabelle zu gelangen. In der Regel sollte eure App natürlich gegen solch einen Angriff, wie z.B. SQL-Injektion vorbereitet sein, aber das ist ja hier nicht das Thema und außerdem hat doch jede App irgendwo eine Schwachstelle. Also gehen wir also von diesem worst-case einmal aus. Stellen wir uns also außerdem vor, ihr hättet die Passwörter einfach unverschlüsselt in der Datenbank hinterlegt. Ein Alptraum. Der Hacker bekommt also alle Login-relevanten Daten jedes Nutzers und kann damit Unfug treiben.
Natürlich speichert niemand die Passwörter in Klartext in der Datenbank ab. Zum Glück gibt es ja One-way Algorithmen, die euer Passwort in einen 16bit-String verschlüsseln. In PHP sind diese sogar von Haus aus eingebaut: md5() und sha1() lassen grüßen. Aber reicht eine einfache Verschlüsselung wirklich aus? Da es sich um One-way Verschlüsselungen handelt, dürfte man ja eigentlich meinen, dass md5 oder sha1 ausreichen sollten. Aber auch diese Methode der Passwortaufberahung hat eine drastische Schwachstelle: Directory-Attacks. Eine Directory-Attack lässt sich mit dem folgenden Code-Schnippsel am einfachsten erklären:
function find_password($password) {
// Ein Array bestehend aus unzähligen möglichen Passwörten
$directory = array();
foreach($directory as $item) {
if(md5($item) == $password) {
echo "Das Passwort lautet: ".$item;
break;
}
}
}
Diese Funktion lädt also ein Directory-Array, loopt dieses mithilfe von foreach und vergleicht den md5-Hash jedes Items mit dem zu knackenden Passwort. Wird eine Übereinstimmung gefunden, wird das Passwort ausgegeben und die Schleife abgebrochen. So einfach ist es also ein einfaches in md5 oder sha1 verschlüsseltest Passwort zu knacken. Was aber tun, um die Passwörter wirklich sicher zu speichern?
Die Antwort klingt erstmal lustig: “Salted sha1-hash”. Was hat das ganze denn mit Salz zu tun?
Beim “salten” von Passwörten geht es darum, dem zu verschlüsselnden String erst noch eine 32-Bit lange Zeichenfolge anzuhängen. Man salzt das Passwort also. Auch hier erklärt das Beispiel viel besser als jede Beschreibung:
var $encryption_key = 'APANtByIGI1BpVXZTJgcsAG8GZl8pdwwa84';
private function _prep_password($password) {
return sha1($password.$this->encryption_key);
}
Die Funktion _prep_password() heftet dem Passwort also noch einen vorher festgelegten String an und verschlüsselt den neu entstandenen String nach dem sha1 Algorithmus. Diesen Hash kann man nun in die Datenbank speichern. Natürlich muss man darauf auchten, alle Funktionen, die die Datenbank nach dem Passwort abfragen, das Passwort erst mit dieser Funktion vorbereiten:
// Hier noch ein Beispiel, wie man dies nun nutzen kann
// Beispiel Query aus einer Login Funktion
$query = 'SELECT * FROM user_table WHERE username = "$username" AND password = "$this->_prep_password($password)" LIMIT 1';
Diese Methode ist sicher gegen Directory-Attacks, da der Hacker die Variable encryption_key kennen müsste, um diese mit in seine Directory Funktion einzubauen. Ob dies nun letztendlich das Nonplusultra der Passwortsicherung ist, dafür möchte ich meine Hand nicht ins Feuer legen, da sich tagtäglich sowohl Hacker bemühen, Sicherheitslücken zu finden, als auch Entwickler, diese wieder zu schließen. In meinen Projekten hat diese Methode bis jetzt immer sehr gut funktioniert, so viel kann ich mich Sicherheit sagen. Sollte es neuere, sichere, bessere Methoden geben, würde ich mich über einen Kommentar freuen!