3 * This is a compatibility file for password_hash and password_verify for
4 * php systems prior to 5.5 that do not have password hashing built-in.
5 * This will export these two functions to the global namespace
7 defined('PASSWORD_BCRYPT') or define('PASSWORD_BCRYPT', 1);
9 defined('PASSWORD_DEFAULT') or define('PASSWORD_DEFAULT', PASSWORD_BCRYPT
);
11 if (! function_exists('password_hash')) {
13 * Hash the password using the specified algorithm
15 * @param string $password
16 * The password to hash
18 * The algorithm to use (Defined by PASSWORD_* constants)
19 * @param array $options
20 * The options for the algorithm to use
22 * @return s string|false The hashed password, or false on error.
24 function password_hash($password, $algo, $options = array())
26 if (! function_exists('crypt')) {
27 trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING
);
31 if (! is_string($password)) {
32 trigger_error("password_hash(): Password must be a string", E_USER_WARNING
);
36 if (! is_int($algo)) {
37 trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING
);
43 // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
45 if (isset($options ['cost'])) {
46 $cost = $options ['cost'];
47 if ($cost < 4 ||
$cost > 31) {
48 trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING
);
53 $required_salt_len = 22;
54 $hash_format = sprintf("$2y$%02d$", $cost);
57 trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING
);
61 if (isset($options ['salt'])) {
62 switch (gettype($options ['salt'])) {
68 $salt = ( string ) $options ['salt'];
71 if (method_exists($options ['salt'], '__tostring')) {
72 $salt = ( string ) $options ['salt'];
76 //NOTE FALL-THROUGH CASE HERE. POSSIBLE BUG.
80 trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING
);
84 if (strlen($salt) < $required_salt_len) {
85 trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING
);
87 } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
88 $salt = str_replace('+', '.', base64_encode($salt));
91 $salt = __password_make_salt($required_salt_len);
94 $salt = substr($salt, 0, $required_salt_len);
96 $hash = $hash_format . $salt;
98 $ret = crypt($password, $hash);
100 if (! is_string($ret) ||
strlen($ret) < 13) {
108 if (! function_exists('password_get_info')) {
110 * Get information about the password hash.
111 * Returns an array of the information
112 * that was used to generate the password hash.
116 * 'algoName' => 'bcrypt',
117 * 'options' => array(
122 * @param string $hash
123 * The password hash to extract info from
125 * @return array The array of information about the hash.
127 function password_get_info($hash)
131 'algoName' => 'unknown',
132 'options' => array ()
134 if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
135 $return ['algo'] = PASSWORD_BCRYPT
;
136 $return ['algoName'] = 'bcrypt';
137 list ( $cost ) = sscanf($hash, "$2y$%d$");
138 $return ['options'] ['cost'] = $cost;
145 if (! function_exists('password_needs_rehash')) {
147 * Determine if the password hash needs to be rehashed according to the options provided
149 * If the answer is true, after validating the password using password_verify, rehash it.
151 * @param string $hash
154 * The algorithm used for new password hashes
155 * @param array $options
156 * The options array passed to password_hash
158 * @return boolean True if the password needs to be rehashed.
160 function password_needs_rehash($hash, $algo, array $options = array())
162 $info = password_get_info($hash);
163 if ($info ['algo'] != $algo) {
168 case PASSWORD_BCRYPT
:
169 $cost = isset($options ['cost']) ?
$options ['cost'] : 10;
170 if ($cost != $info ['options'] ['cost']) {
180 if (! function_exists('password_verify')) {
182 * Verify a password against a hash using a timing attack resistant approach
184 * @param string $password
185 * The password to verify
186 * @param string $hash
187 * The hash to verify against
189 * @return boolean If the password matches the hash
191 function password_verify($password, $hash)
193 if (! function_exists('crypt')) {
194 trigger_error("Crypt must be loaded for password_create to function", E_USER_WARNING
);
198 $ret = crypt($password, $hash);
199 if (! is_string($ret) ||
strlen($ret) != strlen($hash)) {
204 for ($i = 0; $i < strlen($ret); $i ++
) {
205 $status |
= (ord($ret [$i]) ^
ord($hash [$i]));
208 return $status === 0;
213 * Function to make a salt
215 * DO NOT USE THIS FUNCTION DIRECTLY
220 function __password_make_salt($length)
223 trigger_error(sprintf("Length cannot be less than or equal zero: %d", $length), E_USER_WARNING
);
228 $raw_length = ( int ) ($length * 3 / 4 +
1);
229 $buffer_valid = false;
230 if (function_exists('mcrypt_create_iv')) {
231 $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM
);
233 $buffer_valid = true;
237 if (! $buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
238 $buffer = openssl_random_pseudo_bytes($raw_length);
240 $buffer_valid = true;
244 if (! $buffer_valid && file_exists('/dev/urandom')) {
245 $f = @fopen
('/dev/urandom', 'r');
247 $read = strlen($buffer);
248 while ($read < $raw_length) {
249 $buffer .= fread($f, $raw_length - $read);
250 $read = strlen($buffer);
254 if ($read >= $raw_length) {
255 $buffer_valid = true;
260 if (! $buffer_valid) {
261 for ($i = 0; $i < $raw_length; $i ++
) {
262 $buffer .= chr(mt_rand(0, 255));
266 $buffer = str_replace('+', '.', base64_encode($buffer));
267 return substr($buffer, 0, $length);