Skip to content

Instantly share code, notes, and snippets.

@dsibilly
Created June 29, 2011 17:01

Revisions

  1. @marcoarment marcoarment revised this gist Jun 29, 2011. 1 changed file with 12 additions and 49 deletions.
    61 changes: 12 additions & 49 deletions Bcrypt.php
    Original file line number Diff line number Diff line change
    @@ -48,8 +48,19 @@ 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 (! function_exists('openssl_random_pseudo_bytes')) {
    throw new Exception('Bcrypt requires openssl PHP extension');
    }

    if ($work_factor < 4 || $work_factor > 31) $work_factor = self::DEFAULT_WORK_FACTOR;
    return crypt($password, self::generate_blowfish_salt($work_factor));
    $salt =
    '$2a$' . str_pad($work_factor, 2, '0', STR_PAD_LEFT) . '$' .
    substr(
    strtr(base64_encode(openssl_random_pseudo_bytes(16)), '+', '.'),
    0, 22
    )
    ;
    return crypt($password, $salt);
    }

    public static function check($password, $stored_hash, $legacy_handler = NULL)
    @@ -65,52 +76,4 @@ public static function check($password, $stored_hash, $legacy_handler = NULL)
    }

    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;
    }
    }
  2. @marcoarment marcoarment revised this gist Jun 29, 2011. 1 changed file with 20 additions and 33 deletions.
    53 changes: 20 additions & 33 deletions Bcrypt.php
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,8 @@
    - 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
    @@ -26,9 +28,15 @@
    $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'], function($password, $hash) {
    return $hash == sha1($password);
    });
    $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']));
    }
    */

    @@ -41,8 +49,7 @@ 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;
    $random = self::random_bytes(16);
    return crypt($password, self::generate_blowfish_salt($random, $work_factor));
    return crypt($password, self::generate_blowfish_salt($work_factor));
    }

    public static function check($password, $stored_hash, $legacy_handler = NULL)
    @@ -59,33 +66,13 @@ public static function check($password, $stored_hash, $legacy_handler = NULL)

    public static function is_legacy_hash($hash) { return substr($hash, 0, 4) != '$2a$'; }

    public static function random_bytes($count)
    private static function generate_blowfish_salt($work_factor)
    {
    $output = '';
    try {
    if (is_readable('/dev/urandom') && ($fh = fopen('/dev/urandom', 'rb')) ) {
    $output = fread($fh, $count);
    fclose($fh);
    }
    } catch (Exception $e) { }

    if (strlen($output) < $count) {
    $random_state = microtime();
    if (function_exists('getmypid')) $random_state .= getmypid();

    $output = '';
    for ($i = 0; $i < $count; $i += 16) {
    $random_state = md5(microtime() . $random_state, true);
    $output .= $random_state;
    }
    $output = substr($output, 0, $count);
    if (! function_exists('openssl_random_pseudo_bytes')) {
    throw new Exception('Bcrypt requires openssl PHP extension');
    }

    return $output;
    }

    private static function generate_blowfish_salt($input, $work_factor)
    {
    $random = openssl_random_pseudo_bytes(16);

    /*
    This function has been essentially copied verbatim from phpass 0.3.
    @@ -105,20 +92,20 @@ private static function generate_blowfish_salt($input, $work_factor)

    $i = 0;
    do {
    $c1 = ord($input[$i++]);
    $c1 = ord($random[$i++]);
    $output .= $itoa64[$c1 >> 2];
    $c1 = ($c1 & 0x03) << 4;
    if ($i >= 16) {
    $output .= $itoa64[$c1];
    break;
    }

    $c2 = ord($input[$i++]);
    $c2 = ord($random[$i++]);
    $c1 |= $c2 >> 4;
    $output .= $itoa64[$c1];
    $c1 = ($c2 & 0x0f) << 2;

    $c2 = ord($input[$i++]);
    $c2 = ord($random[$i++]);
    $c1 |= $c2 >> 6;
    $output .= $itoa64[$c1];
    $output .= $itoa64[$c2 & 0x3f];
  3. @marcoarment marcoarment revised this gist Jun 29, 2011. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions Bcrypt.php
    Original file line number Diff line number Diff line change
    @@ -87,6 +87,8 @@ public static function random_bytes($count)
    private static function generate_blowfish_salt($input, $work_factor)
    {
    /*
    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
  4. @marcoarment marcoarment created this gist Jun 29, 2011.
    127 changes: 127 additions & 0 deletions Bcrypt.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,127 @@
    <?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
    - 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'], function($password, $hash) {
    return $hash == sha1($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;
    $random = self::random_bytes(16);
    return crypt($password, self::generate_blowfish_salt($random, $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$'; }

    public static function random_bytes($count)
    {
    $output = '';
    try {
    if (is_readable('/dev/urandom') && ($fh = fopen('/dev/urandom', 'rb')) ) {
    $output = fread($fh, $count);
    fclose($fh);
    }
    } catch (Exception $e) { }

    if (strlen($output) < $count) {
    $random_state = microtime();
    if (function_exists('getmypid')) $random_state .= getmypid();

    $output = '';
    for ($i = 0; $i < $count; $i += 16) {
    $random_state = md5(microtime() . $random_state, true);
    $output .= $random_state;
    }
    $output = substr($output, 0, $count);
    }

    return $output;
    }

    private static function generate_blowfish_salt($input, $work_factor)
    {
    /*
    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($input[$i++]);
    $output .= $itoa64[$c1 >> 2];
    $c1 = ($c1 & 0x03) << 4;
    if ($i >= 16) {
    $output .= $itoa64[$c1];
    break;
    }

    $c2 = ord($input[$i++]);
    $c1 |= $c2 >> 4;
    $output .= $itoa64[$c1];
    $c1 = ($c2 & 0x0f) << 2;

    $c2 = ord($input[$i++]);
    $c1 |= $c2 >> 6;
    $output .= $itoa64[$c1];
    $output .= $itoa64[$c2 & 0x3f];
    } while (1);

    return $output;
    }
    }