Skip to content

Instantly share code, notes, and snippets.

@bzikarsky
Created August 11, 2012 11:34
Show Gist options
  • Select an option

  • Save bzikarsky/3323967 to your computer and use it in GitHub Desktop.

Select an option

Save bzikarsky/3323967 to your computer and use it in GitHub Desktop.

Revisions

  1. Benjamin Zikarsky created this gist Aug 11, 2012.
    135 changes: 135 additions & 0 deletions Bcrypt.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,135 @@
    <?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";
    }
    }