-
-
Save dm/2935162 to your computer and use it in GitHub Desktop.
Simple PHP 5.3+ Bcrypt class adapted from phpass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Adaptation by Marco Arment <[email protected]>. | |
Adapted from Portable PHP Password Hashing Framework (phpass) version 0.3: | |
http://www.openwall.com/phpass/ | |
phpass was by Solar Designer <solar at openwall.com> in 2004-2006 and is in | |
the public domain. This adaptation is also in the public domain. | |
THERE IS ABSOLUTELY NO WARRANTY. | |
Major differences from phpass in this adaptation: | |
- Removed support for versions of PHP prior to 5.3 | |
- Removed "portable" hash support, since it was mostly for those earlier | |
versions of PHP | |
- Replaced manual randomness generation with openssl_random_pseudo_bytes() | |
(thanks, Marc Hedlund) | |
- Added convenience features for migrations from legacy password hashes | |
- Refactored to a more appropriate and modern structure and style for PHP 5 | |
Usage example: | |
// In a registration or password-change form: | |
$hash_for_user = Bcrypt::hash($_POST['password']); | |
// In a login form: | |
$is_correct = Bcrypt::check($_POST['password'], $stored_hash_for_user); | |
// In a login form when migrating entries gradually from a legacy SHA-1 hash: | |
$is_correct = Bcrypt::check( | |
$_POST['password'], | |
$stored_hash_for_user, | |
function($password, $hash) { return $hash == sha1($password); } | |
); | |
if ($is_correct && Bcrypt::is_legacy_hash($stored_hash_for_user)) { | |
$user->store_new_hash(Bcrypt::hash($_POST['password'])); | |
} | |
*/ | |
class Bcrypt | |
{ | |
const DEFAULT_WORK_FACTOR = 8; | |
public static function hash($password, $work_factor = 0) | |
{ | |
if (version_compare(PHP_VERSION, '5.3') < 0) throw new Exception('Bcrypt requires PHP 5.3 or above'); | |
if ($work_factor < 4 || $work_factor > 31) $work_factor = self::DEFAULT_WORK_FACTOR; | |
return crypt($password, self::generate_blowfish_salt($work_factor)); | |
} | |
public static function check($password, $stored_hash, $legacy_handler = NULL) | |
{ | |
if (version_compare(PHP_VERSION, '5.3') < 0) throw new Exception('Bcrypt requires PHP 5.3 or above'); | |
if (self::is_legacy_hash($stored_hash)) { | |
if ($legacy_handler) return call_user_func($legacy_handler, $password, $stored_hash); | |
else throw new Exception('Unsupported hash format'); | |
} | |
return crypt($password, $stored_hash) == $stored_hash; | |
} | |
public static function is_legacy_hash($hash) { return substr($hash, 0, 4) != '$2a$'; } | |
private static function generate_blowfish_salt($work_factor) | |
{ | |
if (! function_exists('openssl_random_pseudo_bytes')) { | |
throw new Exception('Bcrypt requires openssl PHP extension'); | |
} | |
$random = openssl_random_pseudo_bytes(16); | |
/* | |
This function has been essentially copied verbatim from phpass 0.3. | |
The last character in our encoded string will | |
only represent 2 bits. While two known implementations of | |
bcrypt will happily accept and correct a salt string which | |
has the 4 unused bits set to non-zero, we do not want to take | |
chances and we also do not want to waste an additional byte | |
of entropy. | |
*/ | |
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |
$output = '$2a$'; | |
$output .= chr(ord('0') + $work_factor / 10); | |
$output .= chr(ord('0') + $work_factor % 10); | |
$output .= '$'; | |
$i = 0; | |
do { | |
$c1 = ord($random[$i++]); | |
$output .= $itoa64[$c1 >> 2]; | |
$c1 = ($c1 & 0x03) << 4; | |
if ($i >= 16) { | |
$output .= $itoa64[$c1]; | |
break; | |
} | |
$c2 = ord($random[$i++]); | |
$c1 |= $c2 >> 4; | |
$output .= $itoa64[$c1]; | |
$c1 = ($c2 & 0x0f) << 2; | |
$c2 = ord($random[$i++]); | |
$c1 |= $c2 >> 6; | |
$output .= $itoa64[$c1]; | |
$output .= $itoa64[$c2 & 0x3f]; | |
} while (1); | |
return $output; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment