Skip to content

Instantly share code, notes, and snippets.

@krypton
Created June 29, 2011 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save krypton/1053525 to your computer and use it in GitHub Desktop.
Save krypton/1053525 to your computer and use it in GitHub Desktop.
Simple PHP 5.3+ Bcrypt class adapted from phpass
<?php
/*
Adaptation by Marco Arment <me@marco.org>.
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