Difference between #1 and #2 of Secure password hashing with bCrypt

unchanged
Title
Secure password hashing with bCrypt
unchanged
Category
How-tos
unchanged
Tags
security, password, hash, hashing, bcrypt, login
changed
Content
In this article I will show you how to implement a secure password hashing
mechanism in your Yii projects using a bCrypt class.

The inspiration for this wiki came from this
[discussion](http://stackoverflow.com/questions/4795385/how-do-you-use-bcrypt-for-hashing-passwords-in-php
"Article") on StackOverflow.

#### bCrypt Class - Save in /protected/vendors

~~~
[php]
class bCrypt {
  private $rounds;
  private $prefix;
  
  public function __construct($prefix = '', $rounds = 12) {
    if(CRYPT_BLOWFISH != 1) {
      throw new Exception("bcrypt not supported in this installation. See
http://php.net/crypt");
    }

    $this->rounds = $rounds;
    $this->prefix = $prefix;
  }

  public function hash($input) {
    $hash = crypt($input, $this->getSalt());

    if(strlen($hash) > 13)
      return $hash;

    return false;
  }

  public function verify($input, $existingHash) {
    $hash = crypt($input, $existingHash);

    return $hash === $existingHash;
  }

  private function getSalt() {
    // the base64 function uses +'s and ending ='s; translate the first, and cut
out the latter
    return sprintf('$2a$%02d$%s', $this->rounds,
substr(strtr(base64_encode($this->getBytes()), '+', '.'), 0, 22));
  }
  
  private function getBytes() {
    $bytes = '';

    if(function_exists('openssl_random_pseudo_bytes') &&
        (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
      $bytes = openssl_random_pseudo_bytes(18);
    }

    if($bytes === '' && is_readable('/dev/urandom') &&
       ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
      $bytes = fread($hRand, 18);
      fclose($hRand);
    }
    
    if($bytes === '') {
      $key = uniqid($this->prefix, true);
      
      // 12 rounds of HMAC must be reproduced / created verbatim, no known
shortcuts.
      // Salsa20 returns more than enough bytes.
      for($i = 0; $i < 12; $i++) {
        $bytes = hash_hmac('salsa20', microtime() . $bytes, $key, true);
        usleep(10);
      }
    }
	return $bytes;
  }
}

?>
~~~
#### Edit main.cfg config file


~~~
[php]
	'import'=>array(
		'application.vendors.*'
	),
~~~
#### User Model

~~~
[php]
protected function afterValidate()
	{
		$this->password = $this->encrypt($this->password);
		return parent::afterValidate();
	}

	public function encrypt($value)
	{
	$enc = NEW bCrypt();
	    return $enc->hash($value);
	}
~~~

#### UserIdentity - Modify password check to the following static method:
~~~
[php]
} else if (!bCrypt::verify($this->password, $user->password)) {
			$this->errorCode=self::ERROR_PASSWORD_INVALID;

~~~


#### Discussion
How does this work?

By hooking into the User model's afterValidate() method we can drop in this code
without any major changes to the UserIdentity or other components. What happens
is that when your login form is submitted the inputs are validated (username,
password). If they pass validation, the password value gets sent through the
encrypt function and returned as a hash for database comparison.

The only remaining work to do is to ensure that your password field in the
database is large enough to hold the values. I recommend using char(60) as field
type.