Skip to content

Instantly share code, notes, and snippets.

@Zegnat
Last active December 19, 2015 09:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zegnat/5935844 to your computer and use it in GitHub Desktop.
Save Zegnat/5935844 to your computer and use it in GitHub Desktop.
Simple PHP 5.3+ Bcrypt class
<?php
/*
Originally by Marco Arment <me@marco.org>.
Edited by Martijn van der Ven <martijn@zegnat.net>.
This code is released in the public domain.
THERE IS ABSOLUTELY NO WARRANTY.
This class is aimed at PHP versions over 5.3 and below 5.5.
For PHP 5.5 see <http://www.php.net/book.password>.
Edits:
1. Removed standalone functions.
2. Use $2y$ instead of $2a$ when available.
<http://www.php.net/security/crypt_blowfish.php>
3. New strict legacy checking (disabled by default),
when enabled $2a$ hashes will be treated as legacy hashes.
4. Use a slow string compare to counter timing attacks.
Always compare 60 characters, the expected length of the hash.
Todo:
1. HMAC with a static key before crypt’ing. See:
<https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines#Password_Storage>
2. Do not return `crypt` in `hash()` before checking for errors.
*/
class Bcrypt
{
const DEFAULT_WORK_FACTOR = 8;
public static function hash($password, $work_factor = 0)
{
if (version_compare(PHP_VERSION, '5.3', '<')) 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;
$salt =
'$2' . (version_compare(PHP_VERSION, '5.3.7', '<') ? 'a' : 'y') . '$' .
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, $strict = false)
{
if (version_compare(PHP_VERSION, '5.3', '<')) throw new Exception('Bcrypt requires PHP 5.3 or above');
if (self::is_legacy_hash($stored_hash, $strict)) {
if ($legacy_handler) return call_user_func($legacy_handler, $password, $stored_hash);
else throw new Exception('Unsupported hash format');
}
$password_length = strlen($password = crypt($password, $stored_hash) . chr(0));
$stored_hash_length = strlen($stored_hash .= chr(0));
$result = $password_length - $stored_hash_length;
for ($i = 0; $i < 60; $i++) {
$result |= (ord($stored_hash[$i % $stored_hash_length]) ^ ord($password[$i % $password_length]));
}
return $result === 0;
}
public static function is_legacy_hash($hash, $strict = false)
{
$prefix = substr($hash, 0, 4);
return $prefix != '$2y$' && ($prefix != '$2a$') != $strict;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment