Freitag, 13. Juli 2012

Sicheres Passwort Hashing

Nachdem in letzter Zeit immer und immer wieder Webseiten vor allem dadurch auffallen, dass sie Passwörter im Klartext speichern, hier zwei Funktionen aus der User-Klasse eines meiner letzten Projekte...

Übrigens: Wenn Ihr bei Klick auf 'Passwort vergessen' von einer Webseite das Passwort im Klartext per Email zugesendet bekommt, macht man dort genau das falsch. Schaut dann mal in deren AGB oder Datenschutzbestimmung, ob sie dort behaupten die Passwörter zu hashen. Oft sind die nur Copy&Paste. Und bittet sie das Verfahren umzustellen. Oder schreibt Heise.

Der Code

Für die meisten Probleme gibt es Standards, so auch für das Passwort-Hashing. Es gibt also keinen Grund sich selbst mit Funktionen wie (der nicht mehr zu benutzenden) md5() und String-Operationen seine eigene crypt-Funktion zu schreiben.

Hier nun also eine Lösung mit php, weil die grade hier rumlag.

    /* Code Licence: Public Domain
     *
     * $this->pwhash wird in der DB gesichert bzw. daraus gelesen.
     * die Konfiguration in $_CONFIG sieht z.B. so aus:
     *
     * // Hier ist ein zufälliger Wert pro Webseite einzutragen:
     * $_CONFIG["pw_sitekey"]="897a-d21lk.a8q";
     * // Und hier der Hash-Also. sha1, sha512, NICHT md5!
     * $_CONFIG["pw_hashalgo"]="sha512";
     *
     */

    public function setPassword($password){
        global $_CONFIG;
        $site_key=$_CONFIG["pw_sitekey"];
        $algo=$_CONFIG["pw_hashalgo"];
        $nonce=uniqid();
        $hash = hash_hmac($algo, $password . $nonce, $site_key);
        $this->pwhash = $nonce.':'.$hash;
    }

    public function verifyPassword($password){
        global $_CONFIG;
        $site_key=$_CONFIG["pw_sitekey"];
        $algo=$_CONFIG["pw_hashalgo"];
        $nonce= strstr($this->pwhash, ':',TRUE);
        $hash = strstr($this->pwhash, ':',FALSE);
        $chash= ":".hash_hmac($algo, $password . $nonce, $site_key);
        return ($hash == $chash);
    }

Demo

Was kommt da nun also heraus? Dazu eine kleine Demo
        //Passwort
        $password="very secret";

        //Konfiguration:
        $site_key="12345";
        $algo="sha512";

        $nonce=uniqid();
        $hash = hash_hmac($algo, $password . $nonce, $site_key);
        $pwhash = $nonce.':'.$hash;

        //ausgabe zur Kontrolle
        echo "pwhash= $pwhash\n";

        //zu pruefendes Passwort:
        $check_password="very secret";

        $vnonce= strstr($pwhash, ':',TRUE);
        $vhash = strstr($pwhash, ':',FALSE);
        $chash= ":".hash_hmac($algo, $check_password . $vnonce, $site_key);

        if ($vhash == $chash){
            echo "passwords match!\n";
        }

Die Ausgabe bei übereinstimmenden Passwörtern:
pwhash= 50000b502b234:b038dfb61ba609604e7be78f3aa2cd84fb03970031f5e8d6f2eacc10542741dda5b7f41a716f3f78c305f6898d9b82838ce6b9df15dc94d00c37af393cc51de1
passwords match!

Passwort vergessen!

Wie setzt man das nun organisatorisch mit dem 'Passwort vergessen' Link zusamen? Ganz einfach. Wenn die Nutzer ihr Passwort vergessen können sie sich an ihre Emailadresse einen Link zusenden, mit dessen Hilfe sie auf der Webseite ihr Passwort selbst zurücksetzen können. Der Link enthält dabei ein zufällig erzeugtes Token und verliert nach Benutzung (erfolgreiches Ändern des Passworts) seine Gültigkeit.

Nachtrag: Und natürlich sollte der Link wenn er nicht benutzt wurde nach X Tagen ebenfalls invalidiert werden.

Kommentare:

Anonym hat gesagt…

Sehr schön, das werde ich doch vermutlich die Tage mal näher angucken und dann vermutlich implementieren!
Aber denkt nicht ich würde Passwörter nicht hashen! :)

Anonym hat gesagt…

Ob man nun MD5 oder einen besseren Algroithmus verwendet, spielt für die Sicherheit nur eine untergeordnete Rolle. In beiden Fällen ist der einzige praktikable Angriff das Durchprobieren von möglichen Passwörtern z.B. mit einem Wörterbuch. Kryptographische Angriffe gegen MD5 können da nicht angewendet werden. Es bleibt also nur der geringfügig höhere Rechenaufwand für die Berechnung des Hashes (SHA1 ist auf Grafikkarten ca. halb so schnell wie MD5). Die Lösung mit einem einfachen Hash bietet keinen ausreichenden Schutz gegen einen Brute-Force Angriff. Durch den Salt muss man zwar jedes Passwort einzeln cracken und kann keine Rainbow-Table verwenden. Durch die enorme Rechenleistung von Grafikkarten lassen sich dennoch viele Passwörter finden. Beispielsweise schafft eine HD7970 Karte ca. 2 Milliarden Versuche pro Sekunde [1]. Damit lassen sich in kurzer Zeit die meisten nutzergenerierten Passwörter in kurzer Zeit knacken. Daher sollte man die Passwortprüfung durch aufwändigere Berechnungen bewusst verlangsamen, um einen Brute-Force Angriff zu verhindern. Eine Möglichkeit dazu ist der bcrypt-Algorithmus [2].

[1] http://hashcat.net/oclhashcat-plus/
[2] https://en.wikipedia.org/wiki/Bcrypt

Florian hat gesagt…

Zu HMAC kann man sich noch die Wikipedia zu Gemüte ziehen - das einzusetzen hat schon Gründe...

http://de.wikipedia.org/wiki/Keyed-Hash_Message_Authentication_Code