phpBuddy

Tutorials und Workshops

Wie kann man Daten in eine Datenbank schreiben und wieder auslesen? Wie schützt man sein Gästebuch vor Spam? Wie erstellt man eine Bildergalerie?
Diese und mehr Fragen werden in der Tutorial Rubrik sehr detailliert und leicht verständlich erklärt.

Sie sind hier: Startseite Bcrypt Klasse
Passwort Hash mit der Bcrypt Klasse

Zunächst etwas Hintergrundinfo zu md5, Bcrypt und Co.
Heutzutage gibt es kaum eine Webanwendung die es einem Besucher nicht ermöglicht ein Benutzerkonto einzurichten. Der Entwickler steht dann vor der Wahl, wie er die Benutzerdaten, und hier im Besonderen das Passwort, eines Benutzers in einer Datenbank abspeichern soll.
Seit vielen Jahren wird das Passwort dazu durch die md5() Funktion gejagt und der so gewonnene Hash wird in die Datenbank gespeichert. Es ist schon seit einiger Zeit bekannt, dass md5() nicht mehr als besonders sicher gilt. Böse Zungen behaupten sogar, dass es als unsicher einzustufen ist. Aus diesem Grund wird dem Benutzerpasswort oft noch ein sogenanntes Salt zugemischt, um das ursprüngliche Passwort sicherer zu machen. Ein Salt sind meistens eine handvoll zufällige Zeichen, die an ein Passwort gehängt werden. Wenngleich dieses Vorgehen das knacken eines Passwort schon erheblich erschwert, bleibt dennoch die Schwachstelle md5().

Stellt sich die Frage, wieso md5() eine Schwachstelle ist?!
Der Hauptgrund liegt vor allem darin, dass md5 ein unheimlich schneller Algorithmus ist. Man füttert eine Zeichenkette in die md5-Funktion und bereits wenige Millisekunden später hat man den Hash. Was gut ist für eine schnelle Webanwendung ist, spielt aber auch Angreifern in die Hände! Diese extrem schnelle md5-Funktion bedeutet nämlich auch, dass ein Angreifer bei einer Brute-Force- oder Wortlisten Attacke mindestens mehrere hundert Zeichenkombinationen in einer Sekunde (!) durchprobieren kann. Und heutige Rechner werden immer schneller, können somit immer mehr Zeichenkombinationen in kürzer werdender Zeit schaffen.

Was kann man also tun?
Die Antwort darauf, zumindest auf absehbare Zeit, heißt Bcrypt. Im Bezug auf PHP ist Bcrypt ein Kunstwort, bzw. eine Abkürzung und steht für crypt() mit dem Algorithmus Blowfish. Dieses Bcrypt gilt derzeit als sicherste Möglichkeit eine Zeichenkette zu encrypten, also einen Hash davon zu erzeugen. Neben einem starken Algorithmus kann man der Funktion auch einen sogenannten Cost-Wert mitteilen. Das ist der Potenzwert für den zugrundeliegenden base-2 Logarithmus, den Blowfish verwendet. Oder anders ausgedrückt; das ist die Anzahl der Wiederholungen, wie häufig der übergebene Wert (Zeichenkette) durch die Funktion gejagt wird um den finalen Hash zu ermitteln. Teilt man also Bcrypt mit es soll z.B. 10 potenzierende Wiederholungen durchführen, wird die Zeichenkette, in unserem Fall das Passwort, 2^10 mal durch die Funktion geschickt. 2^10 bedeutet also 1024 Mal, was auf einem normalen PC ca. 0,25 Sekunden benötigt. Nimmt man 12 Wiederholungen, erhöht sich die Zahl der Durchläufe bereits auf 4096, was ca. 0,8 Sekunden dauert. Wie man sieht wird die Funktion ungemein langsam, je mehr Wiederholungen man durchführt. Genau hier liegt auch der Grund für die extrem hohe Sicherheit. Während md5 den Hash eines Passwort bereits nach wenigen Millisekunden liefert, wodurch ein Angreifer 800 oder mehr Passwörter pro Sekunde durchprobieren kann, schafft man bei Bcrypt gerade mal Eins. Außerdem hat Bcrypt noch ein weiteres Ass im Ärmel: es setzt zwingend ein sehr langes Salt voraus. In der Dokumentation wird 16-stellig angegeben, tatsächlich aber nutzt Bcrypt eine 22-stellige Zeichenkette als Salt.
Bei einem so stark gesalzenen Passwort, in Kombination mit dem Schneckentempo das Bcrypt an den Tag legt, braucht ein Angreifer Monate bis Jahre um ein einziges Passwort zu cracken.
Schauen wir uns nun mal an, wie einfach wir Bcrypt mit dieser Klasse verwenden können.


Die Bcrypt Klasse

Hier zunächst die Klasse, im Anschluss dann ein kurzes Anwendungsbeispiel.

<?php
 
/**
 * phpBuddy.eu Bcrypt Klasse
 *
 * Erzeugt einen Passwort-Hash mittels Blowfish Algorithmus.
 *
 * @author     Andreas Skodzek <webmaster@phpbuddy.eu>
 * @link       http://www.phpbuddy.eu/
 * @copyright  2011 Andreas Skodzek
 * @license    GNU Public License <http://www.gnu.org/licenses/gpl.html>
 * @package    phpBuddy Bcrypt
 * @version    1.0 released 10.02.2011
 */
 
class Bcrypt
{
    /**
     * Konstante mit erlaubten Zeichen für den Passwortzusatz
     */
    const SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
 
    /**
     * @param int  Anzahl der Wiederholungen (Cost). minimal 4, maximal 31
     */
    public static $cost = 12;
 
    /**
     *
     * @param array  Nimmt die Informationen Passwort, Salt, Wiederholungen und Hash auf
     */
    public static $info = array();
 
 
    /**
     * Erzeugt einen Hash aus einem Passwort
     *
     * @param string  $passwort Passwort
     * @param int  $cost Anzahl der Wiederholungen
     * @return array
     */
    public static function hash($passwort, $cost = NULL)
    {
        // Prüfen ob Blowfish vom Server unterstützt wird
        self::check_blowfish();
 
        // Blowfish Algorithmus
        self::$info['algo'] = '$2a$';
 
        // 22-stelligen Salt erzeugen der für Blowfish erforderlich ist
        $tmp_salt = '';
        for ($i = 0; $i <= 21; $i++)
        {
            $tmp_str   = str_shuffle(self::SALTCHARS);
            $tmp_salt .= $tmp_str[0];
        }
 
        // Salt im Info-Array ablegen
        self::$info['salt_string'] = $tmp_salt;
 
        // Anzahl der Wiederholungen ermitteln
        if ($cost)
        {
            // Führende Null voranstellen bei einstelliger Wiederholung
            self::$info['cost'] = sprintf('%02d', min(31, max(intval($cost), 4)));
        }
        else
        {
            // Standard verwenden
            self::$info['cost'] = self::$cost;
        }
 
        // Komplettes Salt mit Algorithmus und Wiederholungen
        self::$info['salt_komplett'] =    self::$info['algo'] . self::$info['cost'] . '$' . self::$info['salt_string'] . '$';
 
        // Passwort Hash erzeugen
        self::$info['hash'] = crypt($passwort, self::$info['salt_komplett']);
 
        // Info-Array zurückgeben
        return self::$info;
    }
 
 
    /**
     * Prüft ein Passwort gegen einen Hash
     *
     * @param string  $passwort Passwort aus dem der zu vergleichende Hash erzeugt werden soll
     * @param string  $hash Der zu vergleichende Hash mit komplettem Salt
     * @return bool
     */
    public static function check_hash($passwort, $hash)
    {
        // Prüfen ob Blowfish vom Server unterstützt wird
        self::check_blowfish();
 
        // Komplettes Salt mit Algorithmus und Wiederholungen aus dem Hash extrahieren
        $tmp_salt = substr($hash, 0, 29);
 
        // Vergleichshash erzeugen
        $tmp_hash = crypt($passwort, $tmp_salt . '$');
 
        // Stimmt das Passwort mit dem Hash überein ist der Rückgabewert TRUE, ansonsten FALSE
        return ($tmp_hash == $hash) ? TRUE : FALSE;
    }
 
 
    /**
     * Prüft ob der Blowfish Algorithmus unterstützt wird
     */
    private static function check_blowfish()
    {
        if ( ! defined('CRYPT_BLOWFISH'))
        {
            throw new Exception('Bcrypt wird von diesem Server leider nicht unterstützt!');
        }
    }
}

Wie man im Kommentar von $cost sehen kann, ist ein Wert zwischen 4 und 31 möglich.
VORSICHT! Hier sollte man nicht zu mutig werden und mit zu hohen Werten für die Wiederholungen spielen. Beim Standardwert von 12 dauert der Hashvorgang ca. 0,7 bis 1 Sekunde. Bereits ab einem Wert von 16 oder mehr hat der Server schon mehrere Minuten zu schwitzen, was sehr wahrscheinlich zu einem Scriptfehler führt, da die Execution Time überschritten wird.
Man sollte also Vernunft walten lassen und einen Wert zwischen 8 und 14 wählen, je nach Serverpower.


Anwendungsbeispiel

<?php
 
header( 'Content-Type: text/html; charset=utf-8' );
error_reporting( 0 );
 
// Klasse einbinden
include 'bcrypt.php';
 
// Anwendungsbeispiel
try
{
    // Hash von Passwort erzeugen
    // Bcrypt::hash($passwort, $wiederholungen)
    // Liefert ein Array mit Algorithmus, Wiederholungen (Cost), Salt String, Salt komplett und Hash zurück
    // Hash ist der Wert der gespeichert wird und den man mit check_hash() gegen ein Passwort vergleicht
    $info = Bcrypt::hash('strenggeheim', 12);
    var_dump($info);
 
 
    // Passwort mit einem Hash (z.B. aus einer Datenbank) vergleichen
    // Bcrypt::hash($passwort, $hash)
    // Stimmt das Passwort mit dem Hash überein liefert die Funktion TRUE, ansonsten FALSE zurück
    var_dump(Bcrypt::check_hash('strenggeheim', $info['hash']));
}
catch (Exception $error)
{
    echo $error->getMessage();
}

Da der Code ausführlich kommentiert ist, sollten keine Fragen mehr offen bleiben.
Dennoch möchte ich einen wichtigen Hinweis geben:

Neben der Klasse und dem Anwendungsbeispiel befindet sich in der Zip Datei auch eine Mini-Anwendung in Form eines sehr schlicht gehaltenen Login Scripts. Dieses Login Script dient ausschließlich der Demonstration und ist nicht dazu gedacht einfach in ein produktives System übernommen zu werden, denn dafür ist es ganz und gar nicht ausgelegt!
Wer das Login Scriptchen testen möchte, findet im Ordner login_demo alle benötigten Datei, inklusive Datenbank Schema. Sobald die Datenbank und die benötigte Tabelle angelegt ist, muss man noch im Verzeichnis include in der Datei datenbank.php die Zugangsdaten eintragen. Danach ist das Script einsatzbereit. Im wesentlichen besteht die Mini-Anwendung aus einem Formular um neue Benutzer anzulegen - das demonstriert, wie man den Hash mit der Bcrypt Klasse erzeugt und wie dieser dann in die DB kommt. Desweiteren eine Login Seite die demonstriert, wie man die eingegebenen Benutzerdaten mit den Daten aus der DB vergleicht. Und letztlich noch eine geschützte Seite, auf der man sich dann auch wieder ausloggen kann.

Nochmal der eindringliche Hinweis, dass dieses Login Script nur zur Anschauung gedacht ist und nicht im produktiven Einsatz eingesetzt werden soll! Ich weiss, einige sind von diesen Hinweisen genervt, aber immer wieder missachten User diese Hinweise, kopieren ohne nötigen Sachverstand 1:1 die Scripts und rennen dann mit dem Kopf gegen eine Wand, weil sie nicht mehr weiter wissen. Anschließend treffen bei mir dann verzweifelte Hilfemails ein, in denen um Anpassungen und Erklärungen gebeten wird.

Viel Spaß mit der Bcrypt Klasse und happy encrypting! :-)

Bcrypt Klasse (8 KB)