Skip to content

Instantly share code, notes, and snippets.

@bzikarsky
Created August 11, 2012 11:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bzikarsky/3323967 to your computer and use it in GitHub Desktop.
Save bzikarsky/3323967 to your computer and use it in GitHub Desktop.
Simple Bcrypt class
<?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