Skip to content

Instantly share code, notes, and snippets.

@kawahara
Created May 9, 2011 15:04
Show Gist options
  • Save kawahara/962689 to your computer and use it in GitHub Desktop.
Save kawahara/962689 to your computer and use it in GitHub Desktop.
<?php
class opPasswordHashException extends Exception {}
/**
* opPasswordHash
*
* This class is based on
* Portable PHP password hashing framework <http://www.openwall.com/phpass/>
* by Solar Designer <solar at openwall.com>.
*
* @package OpenPNE
* @subpackage util
* @author Shogo Kawahara <kawahara@bucyou.net>
*/
class opPasswordHash
{
protected
$itoa64,
$iteration_count_log2,
$portable_hashes,
$random_state,
$hash_algo;
public function __construct($iteration_count_log2, $portable_hashes = true, $options = array())
{
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$this->hash_algo = 'sha512';
$ha = hash_algos();
if (isset($options['is_use_sha512']))
{
$this->is_use_sha512 = (bool)$options['is_use_sha512'];
if ($this->is_use_sha512)
{
if (!in_array('sha512', $ha))
throw new opPasswordHashException();
}
else
{
$this->hash_algo = 'md5';
}
}
else
{
if (!in_array('sha512', $ha))
$this->hash_algo = 'md5';
}
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
$iteration_count_log2 = 8;
$this->iteration_count_log2 = $iteration_count_log2;
$this->portable_hashes = $portable_hashes;
$this->random_state = microtime();
if (function_exists('getmypid'))
$this->random_state .= getmypid();
}
protected function encode64($input, $count)
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count)
$value |= ord($input[$i]) << 8;
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count)
break;
if ($i < $count)
$value |= ord($input[$i]) << 16;
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count)
break;
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
protected function getRandomBytes($count)
{
$output = '';
if (@is_readable('/dev/urandom') &&
($fh = @fopen('/dev/urandom', 'rb')))
{
$output = fread($fh, $count);
fclose($fh);
}
if (strlen($output) < $count)
{
$output = '';
for ($i = 0; $i < $count; $i += 16)
{
$this->random_state = md5(microtime().$this->random_state);
$output .= pack('H*', md5($this->random_state));
}
$output = substr($output, 0, $count);
}
return $output;
}
protected function hashAndStretch($password, $salt, $count, $hash_algo)
{
$hash = hash($hash_algo, $salt.$password, true);
do {
$hash = hash($hash_algo, $salt.$password, true);
} while (--$count);
return $hash;
}
protected function cryptPrivate($password, $setting)
{
$output = '*0';
if (substr($setting, 0, 2) === $output)
$output = '*1';
$id = substr($setting, 0, 3);
if (!in_array($id, array('$P$', '$H$', '$S$')))
return $output;
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30)
return $output;
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) != 8)
return $output;
$hash_algo = $this->hash_algo;
if (in_array($id, array('$P$', '$H$')))
{
$hash_algo = 'md5';
}
$hash = $this->hashAndStretch($password, $salt, $count, $hash_algo);
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, strlen($hash));
return $output;
}
protected function gensaltPrivate($input)
{
$output = 'sha512' === $this->hash_algo ? '$S$' : '$P$';
$output .= $this->itoa64[min($this->iteration_count_log2 + 5, 30)];
$output .= $this->encode64($input, 6);
return $output;
}
public function hashPassword($password)
{
$random = '';
if (strlen($random) < 6)
$random = $this->getRandomBytes(6);
$hash = $this->cryptPrivate($password, $this->gensaltPrivate($random));
$id = substr($hash, 0, 3);
if ('$S$' === $id && 98 === strlen($hash)) return $hash;
if (('$P$' === $id || '$H$' === $id) && 34 === strlen($hash)) return $hash;
return '*';
}
public function checkPassword($password, $stored_hash)
{
$hash = $this->cryptPrivate($password, $stored_hash);
if ($hash[0] === '*')
$hash = crypt($password, $stored_hash);
return $hash === $stored_hash;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment