| @@ -1,317 +0,0 @@ | |||
| <?php | |||
| /** | |||
| * A Compatibility library with PHP 5.5's simplified password hashing API. | |||
| * | |||
| * @author Anthony Ferrara <[email protected]> | |||
| * @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
| * @copyright 2012 The Authors | |||
| */ | |||
| namespace { | |||
| if (!defined('PASSWORD_BCRYPT')) { | |||
| /** | |||
| * PHPUnit Process isolation caches constants, but not function declarations. | |||
| * So we need to check if the constants are defined separately from | |||
| * the functions to enable supporting process isolation in userland | |||
| * code. | |||
| */ | |||
| define('PASSWORD_BCRYPT', 1); | |||
| define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); | |||
| define('PASSWORD_BCRYPT_DEFAULT_COST', 10); | |||
| } | |||
| if (!function_exists('password_hash')) { | |||
| /** | |||
| * Hash the password using the specified algorithm | |||
| * | |||
| * @param string $password The password to hash | |||
| * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) | |||
| * @param array $options The options for the algorithm to use | |||
| * | |||
| * @return string|false The hashed password, or false on error. | |||
| */ | |||
| function password_hash($password, $algo, array $options = array()) { | |||
| if (!function_exists('crypt')) { | |||
| trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); | |||
| return null; | |||
| } | |||
| if (is_null($password) || is_int($password)) { | |||
| $password = (string) $password; | |||
| } | |||
| if (!is_string($password)) { | |||
| trigger_error("password_hash(): Password must be a string", E_USER_WARNING); | |||
| return null; | |||
| } | |||
| if (!is_int($algo)) { | |||
| trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); | |||
| return null; | |||
| } | |||
| $resultLength = 0; | |||
| switch ($algo) { | |||
| case PASSWORD_BCRYPT: | |||
| $cost = PASSWORD_BCRYPT_DEFAULT_COST; | |||
| if (isset($options['cost'])) { | |||
| $cost = (int) $options['cost']; | |||
| if ($cost < 4 || $cost > 31) { | |||
| trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); | |||
| return null; | |||
| } | |||
| } | |||
| // The length of salt to generate | |||
| $raw_salt_len = 16; | |||
| // The length required in the final serialization | |||
| $required_salt_len = 22; | |||
| $hash_format = sprintf("$2y$%02d$", $cost); | |||
| // The expected length of the final crypt() output | |||
| $resultLength = 60; | |||
| break; | |||
| default: | |||
| trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); | |||
| return null; | |||
| } | |||
| $salt_req_encoding = false; | |||
| if (isset($options['salt'])) { | |||
| switch (gettype($options['salt'])) { | |||
| case 'NULL': | |||
| case 'boolean': | |||
| case 'integer': | |||
| case 'double': | |||
| case 'string': | |||
| $salt = (string) $options['salt']; | |||
| break; | |||
| case 'object': | |||
| if (method_exists($options['salt'], '__tostring')) { | |||
| $salt = (string) $options['salt']; | |||
| break; | |||
| } | |||
| case 'array': | |||
| case 'resource': | |||
| default: | |||
| trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); | |||
| return null; | |||
| } | |||
| if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { | |||
| trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); | |||
| return null; | |||
| } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { | |||
| $salt_req_encoding = true; | |||
| } | |||
| } else { | |||
| $buffer = ''; | |||
| $buffer_valid = false; | |||
| if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { | |||
| $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); | |||
| if ($buffer) { | |||
| $buffer_valid = true; | |||
| } | |||
| } | |||
| if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { | |||
| $strong = false; | |||
| $buffer = openssl_random_pseudo_bytes($raw_salt_len, $strong); | |||
| if ($buffer && $strong) { | |||
| $buffer_valid = true; | |||
| } | |||
| } | |||
| if (!$buffer_valid && @is_readable('/dev/urandom')) { | |||
| $file = fopen('/dev/urandom', 'r'); | |||
| $read = 0; | |||
| $local_buffer = ''; | |||
| while ($read < $raw_salt_len) { | |||
| $local_buffer .= fread($file, $raw_salt_len - $read); | |||
| $read = PasswordCompat\binary\_strlen($local_buffer); | |||
| } | |||
| fclose($file); | |||
| if ($read >= $raw_salt_len) { | |||
| $buffer_valid = true; | |||
| } | |||
| $buffer = str_pad($buffer, $raw_salt_len, "\0") ^ str_pad($local_buffer, $raw_salt_len, "\0"); | |||
| } | |||
| if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { | |||
| $buffer_length = PasswordCompat\binary\_strlen($buffer); | |||
| for ($i = 0; $i < $raw_salt_len; $i++) { | |||
| if ($i < $buffer_length) { | |||
| $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); | |||
| } else { | |||
| $buffer .= chr(mt_rand(0, 255)); | |||
| } | |||
| } | |||
| } | |||
| $salt = $buffer; | |||
| $salt_req_encoding = true; | |||
| } | |||
| if ($salt_req_encoding) { | |||
| // encode string with the Base64 variant used by crypt | |||
| $base64_digits = | |||
| 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; | |||
| $bcrypt64_digits = | |||
| './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |||
| $base64_string = base64_encode($salt); | |||
| $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); | |||
| } | |||
| $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); | |||
| $hash = $hash_format . $salt; | |||
| $ret = crypt($password, $hash); | |||
| if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { | |||
| return false; | |||
| } | |||
| return $ret; | |||
| } | |||
| /** | |||
| * Get information about the password hash. Returns an array of the information | |||
| * that was used to generate the password hash. | |||
| * | |||
| * array( | |||
| * 'algo' => 1, | |||
| * 'algoName' => 'bcrypt', | |||
| * 'options' => array( | |||
| * 'cost' => PASSWORD_BCRYPT_DEFAULT_COST, | |||
| * ), | |||
| * ) | |||
| * | |||
| * @param string $hash The password hash to extract info from | |||
| * | |||
| * @return array The array of information about the hash. | |||
| */ | |||
| function password_get_info($hash) { | |||
| $return = array( | |||
| 'algo' => 0, | |||
| 'algoName' => 'unknown', | |||
| 'options' => array(), | |||
| ); | |||
| if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { | |||
| $return['algo'] = PASSWORD_BCRYPT; | |||
| $return['algoName'] = 'bcrypt'; | |||
| list($cost) = sscanf($hash, "$2y$%d$"); | |||
| $return['options']['cost'] = $cost; | |||
| } | |||
| return $return; | |||
| } | |||
| /** | |||
| * Determine if the password hash needs to be rehashed according to the options provided | |||
| * | |||
| * If the answer is true, after validating the password using password_verify, rehash it. | |||
| * | |||
| * @param string $hash The hash to test | |||
| * @param int $algo The algorithm used for new password hashes | |||
| * @param array $options The options array passed to password_hash | |||
| * | |||
| * @return boolean True if the password needs to be rehashed. | |||
| */ | |||
| function password_needs_rehash($hash, $algo, array $options = array()) { | |||
| $info = password_get_info($hash); | |||
| if ($info['algo'] !== (int) $algo) { | |||
| return true; | |||
| } | |||
| switch ($algo) { | |||
| case PASSWORD_BCRYPT: | |||
| $cost = isset($options['cost']) ? (int) $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST; | |||
| if ($cost !== $info['options']['cost']) { | |||
| return true; | |||
| } | |||
| break; | |||
| } | |||
| return false; | |||
| } | |||
| /** | |||
| * Verify a password against a hash using a timing attack resistant approach | |||
| * | |||
| * @param string $password The password to verify | |||
| * @param string $hash The hash to verify against | |||
| * | |||
| * @return boolean If the password matches the hash | |||
| */ | |||
| function password_verify($password, $hash) { | |||
| if (!function_exists('crypt')) { | |||
| trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); | |||
| return false; | |||
| } | |||
| $ret = crypt($password, $hash); | |||
| if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { | |||
| return false; | |||
| } | |||
| $status = 0; | |||
| for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { | |||
| $status |= (ord($ret[$i]) ^ ord($hash[$i])); | |||
| } | |||
| return $status === 0; | |||
| } | |||
| } | |||
| } | |||
| namespace PasswordCompat\binary { | |||
| if (!function_exists('PasswordCompat\\binary\\_strlen')) { | |||
| /** | |||
| * Count the number of bytes in a string | |||
| * | |||
| * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. | |||
| * In this case, strlen() will count the number of *characters* based on the internal encoding. A | |||
| * sequence of bytes might be regarded as a single multibyte character. | |||
| * | |||
| * @param string $binary_string The input string | |||
| * | |||
| * @internal | |||
| * @return int The number of bytes | |||
| */ | |||
| function _strlen($binary_string) { | |||
| if (function_exists('mb_strlen')) { | |||
| return mb_strlen($binary_string, '8bit'); | |||
| } | |||
| return strlen($binary_string); | |||
| } | |||
| /** | |||
| * Get a substring based on byte limits | |||
| * | |||
| * @see _strlen() | |||
| * | |||
| * @param string $binary_string The input string | |||
| * @param int $start | |||
| * @param int $length | |||
| * | |||
| * @internal | |||
| * @return string The substring | |||
| */ | |||
| function _substr($binary_string, $start, $length) { | |||
| if (function_exists('mb_substr')) { | |||
| return mb_substr($binary_string, $start, $length, '8bit'); | |||
| } | |||
| return substr($binary_string, $start, $length); | |||
| } | |||
| /** | |||
| * Check if current PHP version is compatible with the library | |||
| * | |||
| * @return boolean the check result | |||
| */ | |||
| function check() { | |||
| static $pass = NULL; | |||
| if (is_null($pass)) { | |||
| if (function_exists('crypt')) { | |||
| $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'; | |||
| $test = crypt("password", $hash); | |||
| $pass = $test == $hash; | |||
| } else { | |||
| $pass = false; | |||
| } | |||
| } | |||
| return $pass; | |||
| } | |||
| } | |||
| } | |||