Created
August 11, 2012 11:34
-
-
Save bzikarsky/3323967 to your computer and use it in GitHub Desktop.
Simple Bcrypt class
This file contains 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 | |
namespace Zikarsky\Util; | |
class Bcrypt | |
{ | |
const ALGO_ID = "2y"; | |
const ALGO_ID_PRE_5_3_7 = "2a"; | |
const MAX_ITERATIONS = 31; | |
const MIN_ITERATIONS = 4; | |
const DEFAULT_ITERATIONS = 12; | |
const SALT_LENGTH = 22; | |
const DEFINITION_LENGTH = 7; // $__$__$ | |
private $iterations; | |
private $globalSalt; | |
public function __construct($globalSalt, $iterations=self::DEFAULT_ITERATIONS) | |
{ | |
$this->globalSalt = $globalSalt; | |
$this->setIterations($iterations); | |
} | |
public static final function getAlgorithmId() | |
{ | |
return version_compare(PHP_VERSION, "5.3.7", ">=") | |
? self::ALGO_ID | |
: self::ALGO_ID_PRE_5_3_7; | |
} | |
public function getIterations() | |
{ | |
return $this->iterations; | |
} | |
public function setIterations($iterations) | |
{ | |
$iterations = intval($iterations); | |
if ($iterations < self::MIN_ITERATIONS || $iterations > self::MAX_ITERATIONS) { | |
throw new \InvalidArgumentException(sprintf( | |
"\$iterations must be an int greater than %d and lower than %d", | |
self::MAX_ITERATIONS, | |
self::MIN_ITERATIONS | |
)); | |
} | |
$this->iterations = $iterations; | |
} | |
public function getGlobalSalt() | |
{ | |
return $this->globalSalt; | |
} | |
public function hash($password, $userData='', $iterations=null) | |
{ | |
if (is_null($iterations)) { | |
$iterations = $this->iterations; | |
} | |
$string = $this->prepareHashString($password, $userData); | |
$salt = self::makeSalt(self::SALT_LENGTH); | |
$saltDefinition = sprintf('$%s$%02d$%s', | |
self::getAlgorithmId(), $iterations, $salt | |
); | |
return crypt($string, $saltDefinition); | |
} | |
public function checkHash($hash, $password, $userData='') | |
{ | |
$checkHash = crypt( | |
$this->prepareHashString($password, $userData), | |
substr($hash, 0, self::SALT_LENGTH + self::DEFINITION_LENGTH) | |
); | |
return $hash == $checkHash; | |
} | |
protected function prepareHashString($password, $userData) | |
{ | |
return hash_hmac( | |
"whirlpool", | |
str_pad($password, strlen($password) * 4, sha1($userData), STR_PAD_BOTH), | |
$this->globalSalt, | |
true | |
); | |
} | |
public static function makeSalt($length=self::SALT_LENGTH) | |
{ | |
$rand = ''; | |
do { | |
$rand .= sha1(uniqid()); | |
} while (strlen($rand) < $length); | |
return substr($rand, 0, $length); | |
} | |
public static function benchmark($runtimeInMs, $repetitions = 10) | |
{ | |
$bcrypt = new Bcrypt("globalSalt"); | |
for ($it=self::MIN_ITERATIONS; $it<self::MAX_ITERATIONS; $it++) { | |
$time = 0; | |
for ($k=0; $k<$repetitions; $k++) { | |
$start = microtime(true); | |
$bcrypt->hash("password", "userData", $it); | |
$time += (microtime(true) - $start); | |
} | |
$time /= $repetitions; | |
printf("iterations=%02d, avg_time=%s\n", $it, $time); | |
if ($time > $runtimeInMs/1000) { | |
break; | |
} | |
} | |
} | |
public static function test() | |
{ | |
$crypt = new Bcrypt("globalSalt"); | |
$hash = $crypt->hash("password", "userData", 10); | |
echo "hash: $hash\n"; | |
echo "check hash: ", intval($crypt->checkHash($hash, "password", "userData")), "\n"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment