Speicherung von Passwort-Dateien

In Betriebssystemen oder bei Diensten im Internet müssen in irgendeiner Form Informationen über die Passwörter der angemeldeten Nutzer:innen gespeichert werden. Ein erster Ansatz dazu könnte zunächst sein, Passwörter einfach im Klartext in einer einfachen Textdatei zu speichern:

Beispiel für eine Klartext-Passwortdatei passwd.txt:

mustermann:qwertz
falken:joshua
administrator:passw123
root:fmvku76%d
edward:jdjhfZhd8/6ei:dk*askd_ddk?(djsh%hdZH5:dk  

Die Benutzer:in mit dem Login „mustermann“ hat also das Passwort „qwertz“ usw.

Der Nachteil einer solchen Speicherung im Klartext ist natürlich, dass jede, die Lesezugriff auf die Passwortdatei bekommt, sofort die Passwörter von allen Nutzer:innen im Klartext auslesen kann.

Deshalb sollten von verantwortungsbewussten Systemadministrator:innen Passwörter von Nutzer:innen niemals im Klartext in Passwortdateien abgespeichert werden.

Speicherung als Hashwerte

Passwörter lassen sich auch so abspeichern, dass sie nicht im Klartext ausgelesen werden können. Dazu wird eine kryptographische Einwegfunktion, eine sogenannte Hashfunktion, verwendet. Mittels einer solchen Hashfunktion lassen sich Klartextpasswörter leicht und effizient zu Hashwerten verrechnen. Umgekehrt ist es aber praktisch unmöglich, aus einem vorgegebenen Hashwert das Klartextpasswort wieder effizient zurückzuberechnen. Da der Hashwert eines Passwortes praktisch einmalig sind, wird er anschaulich oft auch als dessen Fingerabdruck bezeichnet.

Das sogenannte SHA1-Hashverfahren ordnet einem beliebig langen Text einen 20 Byte langen hexadezimalen Hashwert zu. Für das Passwort des Benutzers „mustermann“ aus dem obigen Beispiel ergibt sich damit der folgende Hashwert:

sha1('qwertz') = 8c829ee6a1ac6ffdbcf8bc0ad72b73795fff34e8

Damit bekommt die gesamte Passwortdatei aus dem obigen Beispiel die folgende Form:

mustermann:8c829ee6a1ac6ffdbcf8bc0ad72b73795fff34e8
falken:d6955d9721560531274cb8f50ff595a9bd39d66f
administrator:4de730479c592c0619802013bc9883dfbde67fea
root:277c21563ade912ffa1f183f04ed482648790fed
edward:712778715c24c68886ecf69d7191cc2acec05919

Enthält eine Passwortdatei oder eine Datenbank statt der Klartext-Passwörter nur ihre Hashwerte, so kann eine potentielle Angreiferin mit diesen Hashwerten erst einmal nichts anfangen, weil sich Hashwerte nicht zurückrechnen lassen. Das gleiche gilt auch für die Systemadministrator:in des Computersystems, die Passwörter nicht im Klartext auslesen kann, obwohl sie natürlich Zugriff auf die Passwortdatei hat. Beachte an dieser Stelle jedoch, dass die Nutzer:in beim späteren Einloggen nicht etwa den Hashwert, sondern ihr Klartext-Passwort in das Passwort-Eingabefeld eingibt. Anschließend wird der Hashwert des eingegebenen Passworts berechnet. Nur wenn dieser berechnete Hashwert mit dem gespeicherten übereinstimmt, wird der Zugang gewährt.

Anmerkung 1

Berechnung von SHA1-Hashwerten in einer GNU/Linux-Shell:

echo -n 'qwertz' | sha1sum

Berechnung von SHA1-Hashwerten in Python:

import hashlib
hashlib.sha1("qwertz".encode('utf-8')).hexdigest()

Anmerkung 2

Aufgrund der Entwicklung immer schnellerer Rechner entspricht das SHA1-Verfahren nicht mehr den heutigen Anforderungen an ein sicheres Hashverfahren. Es wurde im obigen Beispiel lediglich zur Illustration der prinzipiellen Funktionsweise von Passwortdateien verwendet. Aktuelle Verfahren wie SHA512 erzeugen sehr viel längeren Hashwerte, so dass die Übersichtlichkeit der obigen Beispiel-Passwortdatei (für eine menschlichen Leserin) damit erschwert wäre.

Pepper

Werden gehashte Passwörter nach dem oben beschriebenen einfachen Hashverfahren gespeichert, so ergibt sich allerdings das Problem, dass sich die Hashwerte typischer Passwörter vorherberechnen und in sogenannten Rainbowtables speichern lassen. Wird dann zu einem späteren Zeitpunkt eine gehashte Passwortdatei erbeutet, so können die dort gespeicherten Hashwerte einfach mit den vorherberechneten verglichen werden, ohne dass dazu eine erneute zeitaufwendige Berechnung nötig wäre. Findet der Angreifer eine Übereinstimmung von Hashwerten, so hat er damit auch das zugehörige Klartextpasswort gefunden.

Um solche Angriffe auf Passwortdateien mittels Rainbowtables zu erschweren, wird ein sogenanntes Pepper eingesetzt. Dazu werden die Klartextpasswörter pw um ein hinreichend lange Zeichenfolge, das sogenannte Pepper p, ergänzt. Anschließend wird auf die konkatenierte Zeichenfolge p+pw ein Hashverfahren wie z.B. SHA1 angewandt.

    pw = 'qwertz'
    p    = '9cm9hsgkdcucz7ckdje-z7652cv_mnbsdsj_'
    sha1(p+pw) = sha1('9cm9hsgkdcucz7ckdje-z7652cv_mnbsdsj_qwertz') 
               = 5f684e914ba9840cd0a0b2af79251c476ec5ffc2

Mit dem Pepper p = '9cm9hsgkdcucz7ckdje-z7652cv_mnbsdsj_' bekommt die gesamte Passwortdatei aus dem obigen Beispiel damit die folgende Form. Beachte, dass für alle Passwörter das gleiche Pepper p verwendet wird.

    mustermann:5f684e914ba9840cd0a0b2af79251c476ec5ffc2
    falken:08eb33988697de0ad395ba94290e2a324887d0ab
    administrator:a1becea837841197fb847940653c66b84859a576
    root:162b0d49d63b2b280e313ec7ae012ce7871b578a
    edward:6d47b7c8a88bc2e633d05d8defb0b76405859044

Wird nun die Passwortdatei durch einen Angreifer erbeutet, so ist für diesen nicht erkennbar, dass ein Pepper verwendet wurde. Aufgrund der hohen Entropie der um das Pepper verlängerten Klartextpasswörter funktioniert auch ein Angriff über vorherberechnete Rainbowtables nicht mehr.

Eine Schwachstelle des Hashen von Passwörtern mit Pepper entsteht aber, sobald ein Angreifer zusätzlich zur Passwortdatei auch noch das Pepper p erbeutet. Denn dann kann er z.B. Wörterbuch- oder Bruteforceangriffe starten, indem er die zu testenden Passwörter einfach vor dem Hashen um das Pepper ergänzt und damit einen Rainbowtable für das erbeutete Pepper berechnet.

Ein Ausweg daraus bietet wiederum das Hashen der Passwörter mit einem sogenannten Salt:

Salt

Während beim Hashen von Passwörtern mit einem Pepper für die gesamte Passwortdatei ein festes Pepper verwendet wird, wird für Passwortdateien mit Salt für jedes Passwort ein unterschiedlicher sogenanntes Salt s verwendet.

    pw = 'qwertz'
    s    = 'dkkfjerfjc983883(7akfrjklfds8/akdjf=adfsh*'
    sha1(s+pw) = sha1('dkkfjerfjc983883(7akfrjklfds8/akdjf=adfsh*qwertz') 
               = b1abab1a8efe6bd81df13f6f2fa3ae2aadce960d

Die Saltwerte werden dann jeweils zusammen mit dem Hashwert in der Passwortdatei gespeichert, so dass sich für das obige Beispiel dann eine Datei der folgenden Form ergibt:

    mustermann:dkkfjerfjc983883(7akfrjklfds8/akdjf=adfsh*:b1abab1a8efe6bd81df13f6f2fa3ae2aadce960d
    falken:dk38d7/%&jdjdsjd9djsu;(djja8nvmnbx:a53fba661a4e031a7de12bfc55a422d8fbfad6e2
    administrator:aslkfa8e4jfsaksakfdjlalkdsfjdsa:286752d79187249ffa28d2068e460c2c12a8f093
    root:ivcjcxy8737dki9cyyxvjlkj28347askf:2e4fc670daa272fac4bf7d9c67fbdadddf55878a
    edward:aslkfj8vcx7j38h2983hfnc832fjc:55f2ee3ce9296cb9fbaa49581c998c26cbb0c6ce

Da für alle Passwörter unterschiedliche Saltwerte verwendet werden, müsste bei einem Rainbowtable-Angriff für jedes Passwort ein eigener kompletter Rainbowtable berechnet werden. Dies ist so aufwendig, dass ein solcher Angriff nicht effizient möglich ist.