Last active
January 19, 2022 07:50
-
-
Save jeremykendall/8158884 to your computer and use it in GitHub Desktop.
Example of password hashing and verification with password_hash and password_verify. This script is intended to be run from the command line like so: 'php -f password_hash_example.php'
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Example of password hashing and verification with password_hash and | |
* password_verify | |
* | |
* This script is intended to be run from the command line | |
* like so: 'php -f password_hash_example.php' | |
* | |
* @see http://stackoverflow.com/a/20809916/1134565 | |
*/ | |
/** | |
* STEP 1: USER JOINS SITE AND CREATES PASSWORD | |
*/ | |
// User signs up and creates a password: | |
$password = 'SuperSecretPassword!'; | |
$passwordHash = password_hash($password, PASSWORD_DEFAULT); | |
// These are the hashed password's components | |
// password_verify will use this info to recreate the hash created by password_hash() | |
$algo = substr($passwordHash, 0, 4); // $2y$ == BLOWFISH | |
$cost = substr($passwordHash, 4, 2); | |
$salt = substr($passwordHash, 7, 22); | |
/** | |
* STEP 2: USER LOGS INTO SITE | |
*/ | |
// User now attempts to log in with $password: | |
// User provides the password in plain text | |
$plainText = $password; | |
// Password hash created when user signed up is now retireved from database | |
$passwordHashFromDatabase = $passwordHash; | |
// The application will now use password_verify() to recreate the hash and test | |
// it against the hash in the database. | |
$result = password_verify($plainText, $passwordHashFromDatabase); | |
$success = ($result) ? 'True': 'False'; | |
// Output | |
$info = <<<__INFO__ | |
Plain text password: $password | |
Hashed password: $passwordHash | |
Hash components: | |
Algo: $algo | |
Cost: $cost | |
Salt: $salt | |
Login attempt: | |
User returns to log in with password $plainText. | |
Password hash retrieved from DB: $passwordHashFromDatabase | |
Login result: | |
password_verify( | |
$plainText, | |
$passwordHashFromDatabase | |
); | |
Was login successful? $success. | |
__INFO__; | |
echo $info; | |
// password_compat library included here in case password hash functions | |
// not available on your computer | |
// @see https://github.com/ircmaxell/password_compat/blob/master/lib/password.php | |
if (!defined('PASSWORD_DEFAULT')) { | |
define('PASSWORD_BCRYPT', 1); | |
define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); | |
/** | |
* 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_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; | |
} | |
switch ($algo) { | |
case PASSWORD_BCRYPT: | |
// Note that this is a C constant, but not exposed to PHP, so we don't define it here. | |
$cost = 10; | |
if (isset($options['cost'])) { | |
$cost = $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); | |
break; | |
default: | |
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); | |
return null; | |
} | |
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 (strlen($salt) < $required_salt_len) { | |
trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING); | |
return null; | |
} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { | |
$salt = str_replace('+', '.', base64_encode($salt)); | |
} | |
} 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')) { | |
$buffer = openssl_random_pseudo_bytes($raw_salt_len); | |
if ($buffer) { | |
$buffer_valid = true; | |
} | |
} | |
if (!$buffer_valid && is_readable('/dev/urandom')) { | |
$f = fopen('/dev/urandom', 'r'); | |
$read = strlen($buffer); | |
while ($read < $raw_salt_len) { | |
$buffer .= fread($f, $raw_salt_len - $read); | |
$read = strlen($buffer); | |
} | |
fclose($f); | |
if ($read >= $raw_salt_len) { | |
$buffer_valid = true; | |
} | |
} | |
if (!$buffer_valid || strlen($buffer) < $raw_salt_len) { | |
$bl = strlen($buffer); | |
for ($i = 0; $i < $raw_salt_len; $i++) { | |
if ($i < $bl) { | |
$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); | |
} else { | |
$buffer .= chr(mt_rand(0, 255)); | |
} | |
} | |
} | |
$salt = str_replace('+', '.', base64_encode($buffer)); | |
} | |
$salt = substr($salt, 0, $required_salt_len); | |
$hash = $hash_format . $salt; | |
$ret = crypt($password, $hash); | |
if (!is_string($ret) || strlen($ret) <= 13) { | |
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' => 10, | |
* ), | |
* ) | |
* | |
* @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 (substr($hash, 0, 4) == '$2y$' && 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'] != $algo) { | |
return true; | |
} | |
switch ($algo) { | |
case PASSWORD_BCRYPT: | |
$cost = isset($options['cost']) ? $options['cost'] : 10; | |
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) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) { | |
return false; | |
} | |
$status = 0; | |
for ($i = 0; $i < strlen($ret); $i++) { | |
$status |= (ord($ret[$i]) ^ ord($hash[$i])); | |
} | |
return $status === 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In Register
...
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
...
In Login
...
$response["success"] = false;
...
But verification and comparison always become false !
tested with simple password a123. " checked $password=a123 and $colpassword= $2y$10$Fgt9s02vn "
Why password_verify don't work properly and return false ? encrypted password generated correctly ,read from db correctly and confirmed but failed on verification !