3 * The PHPASS password hashing implementation
5 * Use this class to generate and validate PHPASS password hashes.
9 * @see http://www.openwall.com/phpass/
10 * @category PHPCryptLib
12 * @subpackage Implementation
13 * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
14 * @copyright 2011 The Authors
15 * @license http://www.opensource.org/licenses/mit-license.html MIT License
16 * @version Build @@version@@
19 namespace CryptLib\Password\Implementation
;
21 use CryptLib\Random\Factory
as RandomFactory
;
24 * The PHPASS password hashing implementation
26 * Use this class to generate and validate PHPASS password hashes.
28 * @see http://www.openwall.com/phpass/
29 * @category PHPCryptLib
31 * @subpackage Implementation
32 * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
34 class PHPASS
implements \CryptLib\Password\Password
{
37 * @var string The ITOA string to be used for base64 conversion
39 protected static $itoa = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
40 abcdefghijklmnopqrstuvwxyz';
43 * @var Generator The random generator to use for seeds
45 protected $generator = null;
48 * This is the hash function to use. To be overriden by child classes
50 * @var string The hash function to use for this instance
52 protected $hashFunction = 'md5';
55 * @var int The number of iterations to perform (base 2)
57 protected $iterations = 10;
60 * @var string The prefix for the generated hash
62 protected static $prefix = '$P$';
65 * Determine if the hash was made with this method
67 * @param string $hash The hashed data to check
69 * @return boolean Was the hash created by this method
71 public static function detect($hash) {
72 $prefix = preg_quote(static::$prefix, '/');
73 return 1 == preg_match('/^'.$prefix.'[a-zA-Z0-9.\/]{31}$/', $hash);
77 * Return the prefix used by this hashing method
79 * @return string The prefix used
81 public static function getPrefix() {
82 return static::$prefix;
86 * Initialize the password hasher by replacing away spaces in the itoa var
90 public static function init() {
91 static::$itoa = preg_replace('/\s/', '', static::$itoa);
95 * Load an instance of the class based upon the supplied hash
97 * @param string $hash The hash to load from
99 * @return Password the created instance
100 * @throws InvalidArgumentException if the hash wasn't created here
102 public static function loadFromHash($hash) {
103 if (!static::detect($hash)) {
104 throw new \
InvalidArgumentException('Hash Not Created Here');
106 $iterations = static::decodeIterations($hash[3]);
107 return new static($iterations);
111 * Decode an ITOA encoded iteration count
113 * @param string $byte The character to decode
115 * @return int The decoded iteration count (base2)
117 protected static function decodeIterations($byte) {
118 return strpos(static::$itoa, $byte);
122 * Encode a base2 iteration count to a base64 character
126 * @return string The encoded character
128 protected static function encodeIterations($number) {
129 return static::$itoa[$number];
133 * Build a new instance
135 * @param int $iterations The number of times to iterate the hash
136 * @param Generator $generator The random generator to use for seeds
137 * @param Factory $factory The hash factory to use for this instance
141 public function __construct(
143 \CryptLib\Random\Generator
$generator = null
145 if ($iterations > 30 ||
$iterations < 7) {
146 throw new \
InvalidArgumentException('Invalid Iteration Count Supplied');
148 $this->iterations
= $iterations;
149 if (is_null($generator)) {
150 $random = new RandomFactory();
151 $generator = $random->getMediumStrengthGenerator();
153 $this->generator
= $generator;
157 * Create a password hash for a given plain text password
159 * @param string $password The password to hash
161 * @return string The formatted password hash
163 public function create($password) {
164 $salt = $this->to64($this->generator
->generate(6));
165 $prefix = static::encodeIterations($this->iterations
) . $salt;
166 return static::$prefix . $prefix . $this->hash($password, $salt);
170 * Verify a password hash against a given plain text password
172 * @param string $password The password to hash
173 * @param string $hash The supplied ahsh to validate
175 * @return boolean Does the password validate against the hash
177 public function verify($password, $hash) {
178 if (!static::detect($hash)) {
179 throw new \
InvalidArgumentException(
180 'The hash was not created here, we cannot verify it'
183 $iterations = static::decodeIterations($hash[3]);
184 if ($iterations != $this->iterations
) {
185 throw new \
InvalidArgumentException(
186 'Iteration Count Mismatch, Bailing'
189 $salt = substr($hash, 4, 8);
190 $hash = substr($hash, 12);
191 $test = $this->hash($password, $salt);
192 return $test == $hash;
196 * Execute the hash function with proper iterations
198 * @param string $password The password to hash
199 * @param string $salt The salt to use to hash
201 * @return string The base64 encoded generated hash
203 protected function hash($password, $salt) {
204 $count = 1 << $this->iterations
;
205 $hash = hash($this->hashFunction
, $salt . $password, true);
207 $hash = hash($this->hashFunction
, $hash . $password, true);
209 return $this->to64($hash);
213 * Convert the input number to a base64 number of the specified size
215 * @param int $input The number to convert
217 * @return string The converted representation
219 protected function to64($input) {
221 $count = strlen($input);
224 $value = ord($input[$ictr++
]);
225 $output .= static::$itoa[$value & 0x3f];
226 if ($ictr < $count) {
227 $value |
= ord($input[$ictr]) << 8;
229 $output .= static::$itoa[($value >> 6) & 0x3f];
230 if ($ictr++
>= $count) {
233 if ($ictr < $count) {
234 $value |
= ord($input[$ictr]) << 16;
236 $output .= static::$itoa[($value >> 12) & 0x3f];
237 if ($ictr++
< $count) {
238 $output .= static::$itoa[($value >> 18) & 0x3f];
240 } while ($ictr < $count);