Warum man Passwörter hashen muss und wie man das richtig macht

Passwörter speichern, aber richtig!

Passwörter speichern, aber richtig!

Warum man Passwörter hashen muss und wie man das richtig macht

Passwörter speichern, aber richtig!


Wer Passwörter als Klartext speichert, fordert das Unglück heraus. Und auch wer sie nur als Hash speichert, lebt gefährlicher als er es in seinen schlimmsten Albträumen vermuten würde. Aber wie sieht es denn mit gesalzenen Passwort-Hashes aus – versalzen wenigstens die dem Angreifer die Suppe?

Speichern Sie Passwörter als Klartext? Hoffentlich nicht, denn falls ein Angreifer sich zum Beispiel über eine SQL-Injection-Schwachstelle Zugriff auf die Datenbanktabelle mit den Zugangsdaten verschafft, kann er diese Daten dann sofort verwenden. Auch eine Speicherung als einfacher Hash-Wert ist heutzutage nicht mehr ausreichend, und sogar die Speicherung als so genannter gesalzener Hash-Wert ist teilweise nicht mehr sicher.

Aber fangen wir ganz am Anfang an: Was ist eigentlich ein Hash-Wert? Nun, das ist einfach: Ein Hash-Wert ist das Ergebnis einer Hash-Funktion. Die kennen Sie sicher schon, zum Beispiel in Form der in Datenbanken genutzten Hash-Tabellen. Vereinfacht ausgedrückt berechnet eine Hash-Funktion H(M) aus einer beliebig langen Eingabe M, zum Beispiel einem Text, einen möglichst eindeutigen Hash-Wert h fester Länge, zum Beispiel eine Zahlen-Buchstaben-Kombination: h = H(M). Der Hash-Wert kann auch als Fingerprint (Fingerabdruck) der Eingabe bezeichnet werden.

Allgemeine Anforderungen an Hash-Funktionen

An Hash-Funktionen werden eine Reihe von Anforderungen gestellt:

Erste Anforderung an eine Hash-Funktion: Aus einer großen Eingabe wird effizient eine kleine Ausgabe berechnet: Zu gegebenen M ist es leicht, h = H(M) zu berechnen.

Allgemein wird von Hash-Funktionen gefordert, dass sich die Ergebnisse für verschiedene Eingaben mit ausreichend großer Wahrscheinlichkeit unterscheiden: Die Ergebnisse sollen möglichst gleichmäßig auf den definierten Wertebereich verteilt sein. Insbesondere sollen sich die Ausgabewerte für ähnliche Eingabewerte deutlich unterscheiden.

Zweite Anforderung an eine Hash-Funktion: Ähnliche Eingaben führen zu verschiedenen Ausgaben.

Anforderungen speziell an kryptographische Hash-Funktionen

In der Kryptographie kommen drei weitere Anforderungen dazu. Erstens (und damit insgesamt drittens) darf es nicht effizient möglich sein, zu einem gegebenen Hash-Wert eine passende Nachricht zu berechnen.

Dritte Anforderung an eine (Einweg-)Hash-Funktion: Zu gegebenen h ist es schwer, ein M mit H(M) = h zu berechnen.

Hash-Funktionen, die diese Anforderung erfüllen, werden als Einweg-Hash-Funktionen bezeichnet. Dabei wurde die englische Originalbezeichnung one-way leider zu wortgetreu übersetzt, besser wäre Einbahn wie bei der Einbahnstraße gewesen: Die Funktion kann nur in eine Richtung berechnet werden, sie wird ja nicht nach einmaligem Gebrauch unbrauchbar.

Es darf auch nicht effizient möglich sein, zu einer gegebenen Eingabe eine weitere Eingabe mit gleichem Hash-Wert zu finden:

Vierte Anforderung an eine Einweg-Hash-Funktion: Zu gegebenen M ist es schwer, ein anderes M' mit H(M) = H(M') zu berechnen.

Ein Paar Eingabewerte mit gleichem Hash-Wert wird als Kollision bezeichnet. Da die Ausgabemenge kleiner als die Eingabemenge ist, muss es zwangsläufig zu Kollisionen kommen. Eine Hash-Funktion wird daher als kollisionsfrei (manchmal auch passender als kollisionsresistent) bezeichnet, wenn sich die Kollisionen praktisch nicht berechnen lassen.

Fünfte Anforderung an eine kollisionsfrei Einweg-Hash-Funktion: Es ist schwer, zwei beliebige M und M' (M ≠ M') mit H(M) = H(M') zu finden.

Eine gute Übersicht über die Grundlagen von Hash-Funktionen gibt die Doktorarbeit „Analysis and Design of Cryptographic Hash Functions“ von Bart Preneel [1].

Als Beispiel für eine einfache Hash-Funktion ist im Kasten „Eine einfache Einweg-Hash-Funktion: MD2“die Funktion MD2 beschrieben. Der MD2-Algorithmus ist zwar seit Längerem unsicher (mehr dazu in [3]), kann aber immer noch als Beispiel dienen, da er sehr einfach ist.

Hash-Funktionen und Passwörter

Im klassischen Fall läuft die Speicherung der Passwörter bekanntlich folgendermaßen ab:

  • Der Benutzer registriert sich und wählt sein Passwort.

  • Der Hash-Wert des Passworts wird berechnet und zum Beispiel in der Datenbank gespeichert.

  • Wenn der Benutzer sich anmelden möchte, wird der Hash-Wert des eingegebenen Passworts berechnet und mit dem gespeicherten Hash-Wert verglichen. Stimmen beide Hash-Werte überein, wird der Benutzer akzeptiert.

Gelingt es einem Angreifer, sich Zugriff auf die Datenbank zu verschaffen, kann er nur die Hash-Werte ausspähen, mit denen er sich aber nicht bei der Anwendung anmelden kann (sofern es keinen Fehler in der Authentifizierung gibt [4]).

Bei der Speicherung von Passwörtern in Form von Hash-Werten ist also eine Anforderung an die Hash-Funktion besonders wichtig: Sie muss wirklich eine Einwegfunktion sein, sonst könnte ein Angreifer die Funktion einfach umkehren und aus dem ausgespähten Hash-Wert das zugehörige Passwort berechnen und sich damit anmelden.

Auf den ersten Blick scheint auch die Kollisionsfreiheit wichtig zu sein, denn jede Kollision bedeutet ja, dass es mehr als ein Passwort gibt, das einen bestimmten Hash-Wert ergibt, also die Anmeldung als der betroffene Benutzer erlaubt. Wie schon erwähnt, lassen sich Kollisionen nicht vermeiden. Es gibt nun mal deutlich mehr mögliche Passwörter als Hash-Werte zu ihrer Darstellung zur Verfügung stehen. Es wird also immer wieder passieren, dass zwei Benutzer zwar unterschiedliche Passwörter verwenden, diese aber den gleichen Hash-Wert ergeben. Das ist aber normalerweise kein Problem, da diese Benutzer nichts voneinander wissen. Gefährlich wäre so eine Kollision nur, wenn ein bösartiger Benutzer die Hash-Werte ausspäht und dabei feststellt, dass ein anderer Benutzer ein Passwort mit dem gleichen Hash-Wert wie sein eigenes verwendet. Dann könnte er sich sofort mit seinem ihm bekannten Passwort als dieser Benutzer anmelden.

Auf dieses Problem komme ich gleich noch einmal zurück. Betrachten wir aber noch kurz das Thema „Kollisionen und Angriffe“. Bei der Kollisionsfreiheit wird gefordert, dass es nicht einfach möglich sein darf, zwei Eingaben zu finden, die den gleichen Hash-Wert ergeben. Das ist beispielsweise wichtig, wenn der Hash-Wert als Message Authentication Code sicherstellen soll, dass zum Beispiel ein Text nicht verändert wurde (dazu gleich noch etwas mehr). Kann ein Angreifer zum gegebenen Text einen anderen Text finden, der ihm besser gefällt als das Original, der aber den gleichen Hash-Wert ergibt, könnte er die beiden Texte austauschen, ohne dass diese Manipulation auffällt. Im Fall von Passwörtern ist das aber egal, denn wenn der Angreifer bereits ein Passwort für einen bestimmten Hash-Wert kennt, braucht er kein zweites ...