Gefällt dir dieser Artikel?

Passwörter sicher speichern

erschienen in der Kategorie Webdesign, am 13.02.2013
Schnatterente
Ich habe im letzten Beitrag geschrieben, dass der Webhosting Anbieter Evanzo höchstwahrscheinlich die Passwörter seiner Kunden im Klartext speichert. Dies warf die Frage auf, was daran denn falsch sei und wie man es besser machen könne.

Was daran problematisch ist, ist offensichtlich. Jeder der Zugriff auf die Datenbank mit den Kundenlogins hat, sei es ein Mitarbeiter oder ein Hacker, erfährt die Passwörter der Kunden. Viele Kunden haben nur eine E-Mail Adresse und verwenden das gleiche Passwort für verschiedene Internetseiten. Wenn der E-Mail Account selbst mit dem gleichen Passwort geschützt ist, kann ein Krimineller die ganze "Online Identität" eines Nutzers übernehmen. Wie man die Passwörter sicherer speichern kann, erkläre ich im Folgenden.

Eigentlich ist die letzte Aussage in der Einleitung falsch, denn Passwörter sollten aus Gründen der Sicherheit überhaupt nicht gespeichert werden. Stattdessen speichert man ihren Hash-Wert.

Hashwerte und -funktionen

Hash-Funktionen sind mathematische Funktionen, die einen beliebigen Eingabewert auf einen festen Wertebereich abbilden. Etwas weniger hochtrabend ausgedrückt: Es gibt Funktionen, die man mit einem beliebigen Text beliebiger Länge füttern kann und die für jeden solchen Text einen sogenannten Hashwert berechnen. Dieser hat in der Regel immer die gleiche Länge, z.B. 32 alphanumerische Zeichen. Der gleiche Text erzeugt immer den gleichen Hashwert. Ändert man den Text, ändert sich der Hashwert unabhängig davon, ob man nur einen Buchstaben verändert oder ob man gar den ganzen Text durch einen anderen ersetzt. Hier ein kleines Beispiel, indem die Hashsummen mithilfe einer "md5"-Hashfunktion erzeugt wurden. Zu sehen ist jeweils ein Text und dessen Hashsumme.
  • "Schnatterente": 18f2006e206c4fb881c33b2137036e5a
  • "0123456789": 781e5e245d69b566979b86e28d23f2c7
  • "012345678": 22975d8a5ed1b91445f6c55ac121505b
  • "012345677": f50ce2d35116fe4bc9dfa8de7af02305

Hashfunktionen können eingesetzt werden, um Logins zu überprüfen. Das Vorgehen ist sehr einfach. Wenn der Benutzer ein neues Passwort setzt oder ein Passwort für ihn generiert wird, speichert man in der Login-Datenbank nicht das Passwort im Klartext, sondern dessen Hashwert. Wann immer der Nutzer sich einloggen will, erstellt man für das von ihm eingegebene Passwort wieder den Hashwert und überprüft die beiden Werte auf Gleichheit. Somit kann man die Richtigkeit des Logins überprüfen, ohne das Passwort selbst kennen oder speichern zu müssen.

kryptologische Hash-Funktionen

Wenn man über das bisher Gesagte nachdenkt, fällt einem auf, dass es natürlich so sein muss, dass verschiedene Eingabewerte zum gleichen Hashwert führen, denn der Wertebereich der Eingabemenge ist ja viel größer als der der Ausgabemenge (32 Zeichen, die je 36 Werte annehmen können → es gibt 32³⁶ mögliche Hashwerte und unendlich viele Eingabewerte).

Wenn zwei Eingabewerte auf den gleichen Hashwert abbilden, nennt man das eine Kollision. Für den kryptografischen Einsatz, braucht man Hash-Funktionen, die möglichst kollisionsfrei sind. Da dies, wie wir gesehen haben, unmöglich ist, begnügt man sich damit, dass es nahezu unmöglich sein soll, mit vertretbarem Aufwand zwei unterschiedliche Werte zu finden, die eine Kollision erzeugen. Weiterhin ist wichtig, dass es für die Hash-Funktion keine Umkehrfunktion gibt, die anhand eines Hashwertes einen möglichen Eingabewert liefert.

Gesalzene Hashwerte

Selbst wenn man eine kryptografische Hashfunktion verwendet, gibt es aber noch ein Problem. Ein Angreifer könnte sich ein sogenanntes Codebuch erstellen, indem bereits bekannte Zuordnungen von Texten und Hashwerten gespeichert werden. Findet der Angreifer in einer erbeuteten Datenbank einen bekannten Hash, könnte er so Rückschlüsse auf ein passendes Passwort führen (selbst dann, wenn es gar nicht das Passwort ist, dass der Nutzer eingegeben hat, sondern nur eines, dass auf den gleichen Hashwert abgebildet wird).

Um dieses Problem zu beheben, wird das Passwort vor dem Speichern in der Datenbank "gesalzen". Es wird eine zufällige Zeichenfolge (das Salz) erzeugt und diese wird vor dem Hashen an das Passwort (im Klartext) angehängt. Die Zeichenfolge wird natürlich ebenfalls in der Datenbank gespeichert, damit beim Login wieder der gleiche Hashwert erzeugt werden kann.

Für einen Angreifer ist es nun nahezu unmöglich ein Codebuch zu erzeugen, mit dem er an ein geeignetes Passwort kommen könnte. Es müsste ihm dazu gelingen, das Salz wieder aus dem Hashwert "rauszurechnen", um an den Hashwert einer Zeichenfolge zu gelangen, die zusammen mit dem "Salz" wiederum den Hashwert ergibt, den er sich auf illegale Art und Weise beschafft hat. Und dies würde auch nur dann funktionieren, wenn er zufällig an das richtige Passwort geraten wäre, denn auch wenn zwei Wörter zum gleichen Hashwert führen, ergeben sie mit dem gleichen Salz verknüpft wieder unterschiedliche Hashwerte. Da es für kryptografische Hashfunktionen keine Umkehrfunktionen gibt, ist ein solcher Angriff aber weitestgehend aussichtslos.

Ein Beispiel

Angenommen es soll das Passwort "Schnatterente" gespeichert werden.
Würde man es mittels der md5-Funktion hashen, ergäbe dies den Hashwert "18f2006e206c4fb881c33b2137036e5a".

Diesen zu speichern wäre unsicher, denn der Angreifer könnte ein Codebuch haben, indem er diesen Hashwert wiederfindet. Deswegen erzeugen wir ein bisschen Salz (f3gfdfF6gfs34) und hängen es vor dem Hashen an unser Passwort.

"Schnatterentef3gfdfF6gfs34" bildet auf den Hashwert "e6430e00f0951f4ff035985e4866675e" ab. Den erzeugten Hashwert und das Salz speichern wir in unserer Datenbank ab, um den Hashwert anhand der Passworteingabe des Benutzers wieder erzeugen zu können. Gelingt es einem Angreifer unsere Datenbank zu knacken, reichen diese Informationen nicht aus, um an das Passwort des Benutzers zu gelangen.

Gesalzene Hashes in PHP

Wer einen Passwort-Hash mit PHP erzeugen will, sollte sich die crypt-Funktion näher anschauen. Dieser übergibt man das Passwort und das Salz und sie erzeugt den "salted Hash". Das Salz sollte möglichst zufällig gewählt werden. Die folgende Funktion erzeugt einen zufälligen String, anzugebender Länge ($length):

function genRandomString($length) {
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
$string = "";

for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters)-1)];
}

if (strlen($string)!=$length){
$string = genRandomString();
}

return $string;
}


Angenommen wir möchten das Passwort Schnatterente in unserer Datenbank erstellen und dazu Salz der Länge 10 verwenden, so würde der folgende Aufruf den gesalzenen Hashwert erzeugen:

$salt = genRandomString(10);
$pw = "Schnatterente";
$salted_hash = crypt($pw, $salt);

/* Die Variablen $salt und $salted_hash werden nachfolgend in der Datenbank gespeichert */

md5, sha512 und Co

Zu Anschauungszwecken habe ich in diesem Artikel die md5-Hashfunktion verwendet (diese eignet sich gut, weil die erzeugten Hashes 'nur' 32 Zeichen lang sind). Man sollte md5 aber eigentlich nicht mehr nutzen, da der Algorithmus gebrochen wurde. Empfehlenswert ist die Verwendung von sha512 (SHA = Secure Hash Algorithm). Wer ganz sicher gehen will, kann auch noch die Anzahl der Runden erhöhen. Um den sha512 Algorithmus mit der crypt-Funktion von PHP zu nutzen, erzeugt man den Hash so:

$salted_hash = crypt($pw, '$6$rounds=5000$'.$salt.'$');


Das wäre eigentlich Alles, was man zum Speichern von Passwörtern wissen sollte, um nicht gleich auf die Nase zu fallen. :)

Geschnatter

2 Kommentare, selbst mitschnattern << < Seite 1/1 > >>
Martin, am 13.02.2013 um 17:29 Uhr
"Gesalzene" MD5 Hashes sind hier ok, generell sollte man MD5 aber nicht mehr verwenden, da es gebrochen ist.
Empfehlenswert ist SHA512. Dort kann man auch noch die Anzahl der Runden erhöhen, je nachdem wie paranoid man ist.

if (CRYPT_SHA512 == 1) {
echo 'SHA-512: ' . crypt('Schnatterente', '$6$rounds=5000$DeinSalzHier$') . "\n";
}
(Modifiziertes Beispiel von http://php.net/manual/de/function.crypt.php)

Der gewünschte Hashalgorithmus wird crypt() über die Zahl zwischen den beiden ersten Dollarzeichen mittgeteilt. 6 steht hier für SHA512. MD5 wäre 1.
Anonym, am 01.03.2013 um 11:55 Uhr
Sehr hilfreich, Danke dafür!