PHP bringt prinzipiell alles mit, um anständiges Hashing zu betreiben. bcrypt ist der Way To Go, aber auch ohne bcrypt kann man mit vernünftigem Einsatz der vorhanden Hashing-Algorithmen (Iterationen + Salt) gut zurechtkommen – Symfony machts im MessageDigestPasswordEncoder.php richtig. Hauptproblem ist aber, dass vom Durchschnitts-Nutzer zuviel Wissen abverlangt wird, um wirklich sicheres Hashing zu betreiben. Deswegen schickt sich jetzt in PHP 5.5 eine Hashing-Library an, es besser zu machen. Bis es soweit ist und die Funktionalität im Core ankommt, gibt es für PHP >= 5.3.7 eine Compatiblity-Library, die das Verhalten der kommenden Hashing-Funktionen nachbildet.
Die Signaturen der Funktionen aus der Compat-Library sind wie folgt:
password_hash($password, $algo, $options = array())
Wobei im Options-Array der cost-Factor von bcrypt und das salt spezifiziert wird. Verwendung dann wie folgt:
$password = "foo"; $opts = array("cost" => 15, "salt" => "this is my salt, that I use for salting"); $hash = password_hash($password, PASSWORD_BCRYPT, $opts); //$2y$12$dGhpcyBpcyBteSBzYWx0L.QLYHdN06l.OhslYu9VilOYVwFApNezu
Weiter gehts mit password_get_info:
password_get_info($hash)
Da der Cost-Faktor und der Hashing-Algorithmus mit im Hash gespeichert wird (was KEIN Sicherheitsverlust ist, da es nur um das Erschweren von Rainbowtable-Angriffen geht), können wir die Infos natürlich auch wieder auslesen:
$hash = "$2y$12$dGhpcyBpcyBteSBzYWx0L.QLYHdN06l.OhslYu9VilOYVwFApNezu"; print_r(password_get_info($hash)); /* Array ( [algo] => 1 [algoName] => bcrypt [options] => Array ( [cost] => 12 ) ) */
Darauf aufbauend lässt sich auch ermitteln, ob ein Passwort neu gehasht werden muss, etwa weil sich der Cost-Faktor geändert hat.
password_needs_rehash($hash, $algo, array $options = array())
Wichtig: Das salt wird hierbei nicht beachtet.
$opts = array("cost" => 13); var_dump(password_needs_rehash($hash, PASSWORD_BCRYPT, $opts)); //bool(true)
Und natürlich die Verifikation, die einfacher kaum ausfallen könnte:
password_verify($password, $hash)
In der Praxis:
var_dump(password_verify("foo", '$2y$15$dGhpcyBpcyBteSBzYWx0L.TvUDgIgEuPSAJGRCDlJKS8ZI/HaKF4S')); //bool(true)
Das vollständige RFC, weitere Beispiele und eine umfassende Erkärung gibts im PHP-Wiki. Eine gute Entwicklung!
Und so einfach ist es wirklich, komisch dass dies oft einfach nicht gemacht wird…
(kleine Korrektur?)
$opts = array("cost" => 15, "salt" => "this is my salt, that I use for salting");
legt einen Cost-Factor von 15 fest, daraus sollte dann kein Hash wie$2y$12$dGhpcyBpcyBteSBzYWx0L.QLYHdN06l.OhslYu9VilOYVwFApNezu
entstehen. Im letzten Code-Snippet ist dann die 15 wieder dabei.