Created
May 9, 2011 15:04
-
-
Save kawahara/962689 to your computer and use it in GitHub Desktop.
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 | |
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