Created
April 21, 2012 19:35
-
-
Save cballou/2439237 to your computer and use it in GitHub Desktop.
Secure PHP Authentication Revisited - Using bcrypt
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 | |
/** | |
* The code below shows example usage of the SecureHash class for | |
* encrypting a password. In terms of additional usage, you should | |
* store the resulting encrypted password in addition to the salt | |
* in your db. | |
*/ | |
// load the class | |
$secure = new SecureHash(); | |
// the password to encrypt | |
$pass = 'example p@ssword'; | |
// salt is passed by reference and generated on the fly | |
$salt = ''; | |
// the encrypted version of the password (for database storage) | |
$encrypted = $secure->create_hash($pass, $salt); | |
// salt is now populated | |
echo $salt . PHP_EOL; | |
// now store both the $encrypted and $salt values in the database |
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 | |
/** | |
* Below I am simulating the retrieval of POSTed form data containing a email/username and password | |
* supplied by a client. While we don't cover this in the encryption class, the first step | |
* during the login process is to verify that an account exists matching the email address or username | |
* and then to use the stored encrypted password and salt from the matching account to | |
* test for a password match. | |
* | |
* @param string $email A POSTed email address coming from a login form | |
* @param string $password A POSTed password coming from a login form | |
* @return void | |
*/ | |
function loginAction($email, $password) | |
{ | |
// load your user database model | |
$User = new User_Model(); | |
// check for existing account (this entails a query, example in Zend) | |
$account = $User->getByEmailAddress($email); | |
if ($account !== FALSE) { | |
// load the class | |
$secure = new SecureHash(); | |
// check for password match | |
if ($secure->validate_hash($password, $account['password'], $account['salt'])) { | |
// success, log the user in and redirect | |
echo 'The encrypted password matched.' . PHP_EOL; | |
} else { | |
// error, password mismatch | |
echo 'The password did not match our records.' . PHP_EOL; | |
} | |
} | |
} |
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 | |
class SecureHash | |
{ | |
/** | |
* Creates a very secure hash. Uses blowfish by default with a fallback on SHA512. | |
* | |
* Usage of CRYPT_BLOWFISH: | |
* ----------------------- | |
* Blowfish hashing with a salt is as follows: | |
* "$2a$" + a two digit cost parameter + "$" + 22 digits from the base64 alphabet | |
* "./0-9A-Za-z" + "$". | |
* | |
* Using characters outside of this range in the salt will cause crypt() to return | |
* a zero-length string. The two digit cost parameter is the base-2 logarithm | |
* of the iteration count for the underlying Blowfish-based hashing algorithmeter | |
* and must be in range 04-31, values outside this range will cause crypt() to fail. | |
* | |
* @access public | |
* @param string $password | |
* @param string $salt | |
* @param int $stretch_cost | |
*/ | |
public function create_hash($password, &$salt = '', $stretch_cost = 10) | |
{ | |
$salt = strlen($salt) != 21 ? $this->_create_salt() : $salt; | |
if (function_exists('crypt') && defined('CRYPT_BLOWFISH')) { | |
return crypt($password, '$2a$' . $stretch_cost . '$' . $salt . '$'); | |
} | |
// fallback encryption | |
if (!function_exists('hash') || !in_array('sha512', hash_algos())) { | |
throw new Exception('You must have the PHP PECL hash module installed or use PHP 5.1.2+'); | |
} | |
return $this->_create_hash($password, $salt); | |
} | |
/** | |
* @param string $pass The user submitted password | |
* @param string $hashed_pass The hashed password pulled from the database | |
* @param string $salt The salt used to generate the encrypted password | |
*/ | |
public function validate_hash($pass, $hashed_pass, $salt) | |
{ | |
return $hashed_pass === $this->create_hash($pass, $salt); | |
} | |
/** | |
* Create a new salt string which conforms to the requirements of CRYPT_BLOWFISH. | |
* | |
* @access protected | |
* @return string | |
*/ | |
protected function _create_salt() | |
{ | |
$salt = $this->_pseudo_rand(128); | |
return substr(preg_replace('/[^A-Za-z0-9_]/is', '.', base64_encode($salt)), 0, 21); | |
} | |
/** | |
* Generates a secure, pseudo-random password with a safe fallback. | |
* | |
* @access public | |
* @param int $length | |
*/ | |
protected function _pseudo_rand($length) | |
{ | |
if (function_exists('openssl_random_pseudo_bytes')) { | |
$is_strong = false; | |
$rand = openssl_random_pseudo_bytes($length, $is_strong); | |
if ($is_strong === true) return $rand; | |
} | |
$rand = ''; | |
$sha = ''; | |
for ($i = 0; $i < $length; $i++) { | |
$sha = hash('sha256', $sha . mt_rand()); | |
$chr = mt_rand(0, 62); | |
$rand .= chr(hexdec($sha[$chr] . $sha[$chr + 1])); | |
} | |
return $rand; | |
} | |
/** | |
* Fall-back SHA512 hashing algorithm with stretching. | |
* | |
* @access private | |
* @param string $password | |
* @param string $salt | |
* @return string | |
*/ | |
private function _create_hash($password, $salt) | |
{ | |
$hash = ''; | |
for ($i = 0; $i < 20000; $i++) { | |
$hash = hash('sha512', $hash . $salt . $password); | |
} | |
return $hash; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment