3 * A Compatibility library with PHP 5.5's simplified password hashing API.
5 * @author Anthony Ferrara <ircmaxell@php.net>
6 * @license http://www.opensource.org/licenses/mit-license.html MIT License
7 * @copyright 2012 The Authors
12 if (!defined('PASSWORD_BCRYPT')) {
14 * PHPUnit Process isolation caches constants, but not function declarations.
15 * So we need to check if the constants are defined separately from
16 * the functions to enable supporting process isolation in userland
19 define('PASSWORD_BCRYPT', 1);
20 define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
21 define('PASSWORD_BCRYPT_DEFAULT_COST', 10);
24 if (!function_exists('password_hash')) {
27 * Hash the password using the specified algorithm
29 * @param string $password The password to hash
30 * @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
31 * @param array $options The options for the algorithm to use
33 * @return string|false The hashed password, or false on error.
35 function password_hash($password, $algo, array $options = array()) {
36 if (!function_exists('crypt')) {
37 trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
40 if (is_null($password) || is_int($password)) {
41 $password = (string) $password;
43 if (!is_string($password)) {
44 trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
48 trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
54 $cost = PASSWORD_BCRYPT_DEFAULT_COST;
55 if (isset($options['cost'])) {
56 $cost = $options['cost'];
57 if ($cost < 4 || $cost > 31) {
58 trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
62 // The length of salt to generate
64 // The length required in the final serialization
65 $required_salt_len = 22;
66 $hash_format = sprintf("$2y$%02d$", $cost);
67 // The expected length of the final crypt() output
71 trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
74 $salt_req_encoding = false;
75 if (isset($options['salt'])) {
76 switch (gettype($options['salt'])) {
82 $salt = (string) $options['salt'];
85 if (method_exists($options['salt'], '__tostring')) {
86 $salt = (string) $options['salt'];
92 trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
95 if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
96 trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
98 } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
99 $salt_req_encoding = true;
103 $buffer_valid = false;
104 if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
105 $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
107 $buffer_valid = true;
110 if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
111 $buffer = openssl_random_pseudo_bytes($raw_salt_len);
113 $buffer_valid = true;
116 if (!$buffer_valid && @is_readable('/dev/urandom')) {
117 $file = fopen('/dev/urandom', 'r');
118 $read = PasswordCompat\binary\_strlen($buffer);
119 while ($read < $raw_salt_len) {
120 $buffer .= fread($file, $raw_salt_len - $read);
121 $read = PasswordCompat\binary\_strlen($buffer);
124 if ($read >= $raw_salt_len) {
125 $buffer_valid = true;
128 if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
129 $buffer_length = PasswordCompat\binary\_strlen($buffer);
130 for ($i = 0; $i < $raw_salt_len; $i++) {
131 if ($i < $buffer_length) {
132 $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
134 $buffer .= chr(mt_rand(0, 255));
139 $salt_req_encoding = true;
141 if ($salt_req_encoding) {
142 // encode string with the Base64 variant used by crypt
144 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
146 './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
148 $base64_string = base64_encode($salt);
149 $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
151 $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);
153 $hash = $hash_format . $salt;
155 $ret = crypt($password, $hash);
157 if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) {
165 * Get information about the password hash. Returns an array of the information
166 * that was used to generate the password hash.
170 * 'algoName' => 'bcrypt',
171 * 'options' => array(
172 * 'cost' => PASSWORD_BCRYPT_DEFAULT_COST,
176 * @param string $hash The password hash to extract info from
178 * @return array The array of information about the hash.
180 function password_get_info($hash) {
183 'algoName' => 'unknown',
184 'options' => array(),
186 if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
187 $return['algo'] = PASSWORD_BCRYPT;
188 $return['algoName'] = 'bcrypt';
189 list($cost) = sscanf($hash, "$2y$%d$");
190 $return['options']['cost'] = $cost;
196 * Determine if the password hash needs to be rehashed according to the options provided
198 * If the answer is true, after validating the password using password_verify, rehash it.
200 * @param string $hash The hash to test
201 * @param int $algo The algorithm used for new password hashes
202 * @param array $options The options array passed to password_hash
204 * @return boolean True if the password needs to be rehashed.
206 function password_needs_rehash($hash, $algo, array $options = array()) {
207 $info = password_get_info($hash);
208 if ($info['algo'] != $algo) {
212 case PASSWORD_BCRYPT:
213 $cost = isset($options['cost']) ? $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST;
214 if ($cost != $info['options']['cost']) {
223 * Verify a password against a hash using a timing attack resistant approach
225 * @param string $password The password to verify
226 * @param string $hash The hash to verify against
228 * @return boolean If the password matches the hash
230 function password_verify($password, $hash) {
231 if (!function_exists('crypt')) {
232 trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
235 $ret = crypt($password, $hash);
236 if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) {
241 for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
242 $status |= (ord($ret[$i]) ^ ord($hash[$i]));
245 return $status === 0;
251 namespace PasswordCompat\binary {
253 if (!function_exists('PasswordCompat\\binary\\_strlen')) {
256 * Count the number of bytes in a string
258 * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
259 * In this case, strlen() will count the number of *characters* based on the internal encoding. A
260 * sequence of bytes might be regarded as a single multibyte character.
262 * @param string $binary_string The input string
265 * @return int The number of bytes
267 function _strlen($binary_string) {
268 if (function_exists('mb_strlen')) {
269 return mb_strlen($binary_string, '8bit');
271 return strlen($binary_string);
275 * Get a substring based on byte limits
279 * @param string $binary_string The input string
284 * @return string The substring
286 function _substr($binary_string, $start, $length) {
287 if (function_exists('mb_substr')) {
288 return mb_substr($binary_string, $start, $length, '8bit');
290 return substr($binary_string, $start, $length);
294 * Check if current PHP version is compatible with the library
296 * @return boolean the check result
301 if (is_null($pass)) {
302 if (function_exists('crypt')) {
303 $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
304 $test = crypt("password", $hash);
305 $pass = $test == $hash;