Commit | Line | Data |
---|---|---|
14d4f286 S |
1 | <?php |
2 | /** | |
3 | * The PHPASS password hashing implementation | |
4 | * | |
5 | * Use this class to generate and validate PHPASS password hashes. | |
6 | * | |
7 | * PHP version 5.3 | |
8 | * | |
9 | * @see http://www.openwall.com/phpass/ | |
10 | * @category PHPCryptLib | |
11 | * @package Password | |
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@@ | |
17 | */ | |
18 | ||
19 | namespace CryptLib\Password\Implementation; | |
20 | ||
21 | use CryptLib\Random\Factory as RandomFactory; | |
22 | ||
23 | /** | |
24 | * The PHPASS password hashing implementation | |
25 | * | |
26 | * Use this class to generate and validate PHPASS password hashes. | |
27 | * | |
28 | * @see http://www.openwall.com/phpass/ | |
29 | * @category PHPCryptLib | |
30 | * @package Password | |
31 | * @subpackage Implementation | |
32 | * @author Anthony Ferrara <ircmaxell@ircmaxell.com> | |
33 | */ | |
34 | class PHPASS implements \CryptLib\Password\Password { | |
35 | ||
36 | /** | |
37 | * @var string The ITOA string to be used for base64 conversion | |
38 | */ | |
39 | protected static $itoa = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ | |
40 | abcdefghijklmnopqrstuvwxyz'; | |
41 | ||
42 | /** | |
43 | * @var Generator The random generator to use for seeds | |
44 | */ | |
45 | protected $generator = null; | |
46 | ||
47 | /** | |
48 | * This is the hash function to use. To be overriden by child classes | |
49 | * | |
50 | * @var string The hash function to use for this instance | |
51 | */ | |
52 | protected $hashFunction = 'md5'; | |
53 | ||
54 | /** | |
55 | * @var int The number of iterations to perform (base 2) | |
56 | */ | |
57 | protected $iterations = 10; | |
58 | ||
59 | /** | |
60 | * @var string The prefix for the generated hash | |
61 | */ | |
62 | protected static $prefix = '$P$'; | |
63 | ||
64 | /** | |
65 | * Determine if the hash was made with this method | |
66 | * | |
67 | * @param string $hash The hashed data to check | |
68 | * | |
69 | * @return boolean Was the hash created by this method | |
70 | */ | |
71 | public static function detect($hash) { | |
72 | $prefix = preg_quote(static::$prefix, '/'); | |
73 | return 1 == preg_match('/^'.$prefix.'[a-zA-Z0-9.\/]{31}$/', $hash); | |
74 | } | |
75 | ||
76 | /** | |
77 | * Return the prefix used by this hashing method | |
78 | * | |
79 | * @return string The prefix used | |
80 | */ | |
81 | public static function getPrefix() { | |
82 | return static::$prefix; | |
83 | } | |
84 | ||
85 | /** | |
86 | * Initialize the password hasher by replacing away spaces in the itoa var | |
87 | * | |
88 | * @return void | |
89 | */ | |
90 | public static function init() { | |
91 | static::$itoa = preg_replace('/\s/', '', static::$itoa); | |
92 | } | |
93 | ||
94 | /** | |
95 | * Load an instance of the class based upon the supplied hash | |
96 | * | |
97 | * @param string $hash The hash to load from | |
98 | * | |
99 | * @return Password the created instance | |
100 | * @throws InvalidArgumentException if the hash wasn't created here | |
101 | */ | |
102 | public static function loadFromHash($hash) { | |
103 | if (!static::detect($hash)) { | |
104 | throw new \InvalidArgumentException('Hash Not Created Here'); | |
105 | } | |
106 | $iterations = static::decodeIterations($hash[3]); | |
107 | return new static($iterations); | |
108 | } | |
109 | ||
110 | /** | |
111 | * Decode an ITOA encoded iteration count | |
112 | * | |
113 | * @param string $byte The character to decode | |
114 | * | |
115 | * @return int The decoded iteration count (base2) | |
116 | */ | |
117 | protected static function decodeIterations($byte) { | |
118 | return strpos(static::$itoa, $byte); | |
119 | } | |
120 | ||
121 | /** | |
122 | * Encode a base2 iteration count to a base64 character | |
123 | * | |
124 | * @param int $number | |
125 | * | |
126 | * @return string The encoded character | |
127 | */ | |
128 | protected static function encodeIterations($number) { | |
129 | return static::$itoa[$number]; | |
130 | } | |
131 | ||
132 | /** | |
133 | * Build a new instance | |
134 | * | |
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 | |
138 | * | |
139 | * @return void | |
140 | */ | |
141 | public function __construct( | |
142 | $iterations = 8, | |
143 | \CryptLib\Random\Generator $generator = null | |
144 | ) { | |
145 | if ($iterations > 30 || $iterations < 7) { | |
146 | throw new \InvalidArgumentException('Invalid Iteration Count Supplied'); | |
147 | } | |
148 | $this->iterations = $iterations; | |
149 | if (is_null($generator)) { | |
150 | $random = new RandomFactory(); | |
151 | $generator = $random->getMediumStrengthGenerator(); | |
152 | } | |
153 | $this->generator = $generator; | |
154 | } | |
155 | ||
156 | /** | |
157 | * Create a password hash for a given plain text password | |
158 | * | |
159 | * @param string $password The password to hash | |
160 | * | |
161 | * @return string The formatted password hash | |
162 | */ | |
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); | |
167 | } | |
168 | ||
169 | /** | |
170 | * Verify a password hash against a given plain text password | |
171 | * | |
172 | * @param string $password The password to hash | |
173 | * @param string $hash The supplied ahsh to validate | |
174 | * | |
175 | * @return boolean Does the password validate against the hash | |
176 | */ | |
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' | |
181 | ); | |
182 | } | |
183 | $iterations = static::decodeIterations($hash[3]); | |
184 | if ($iterations != $this->iterations) { | |
185 | throw new \InvalidArgumentException( | |
186 | 'Iteration Count Mismatch, Bailing' | |
187 | ); | |
188 | } | |
189 | $salt = substr($hash, 4, 8); | |
190 | $hash = substr($hash, 12); | |
191 | $test = $this->hash($password, $salt); | |
192 | return $test == $hash; | |
193 | } | |
194 | ||
195 | /** | |
196 | * Execute the hash function with proper iterations | |
197 | * | |
198 | * @param string $password The password to hash | |
199 | * @param string $salt The salt to use to hash | |
200 | * | |
201 | * @return string The base64 encoded generated hash | |
202 | */ | |
203 | protected function hash($password, $salt) { | |
204 | $count = 1 << $this->iterations; | |
205 | $hash = hash($this->hashFunction, $salt . $password, true); | |
206 | do { | |
207 | $hash = hash($this->hashFunction, $hash . $password, true); | |
208 | } while (--$count); | |
209 | return $this->to64($hash); | |
210 | } | |
211 | ||
212 | /** | |
213 | * Convert the input number to a base64 number of the specified size | |
214 | * | |
215 | * @param int $input The number to convert | |
216 | * | |
217 | * @return string The converted representation | |
218 | */ | |
219 | protected function to64($input) { | |
220 | $output = ''; | |
221 | $count = strlen($input); | |
222 | $ictr = 0; | |
223 | do { | |
224 | $value = ord($input[$ictr++]); | |
225 | $output .= static::$itoa[$value & 0x3f]; | |
226 | if ($ictr < $count) { | |
227 | $value |= ord($input[$ictr]) << 8; | |
228 | } | |
229 | $output .= static::$itoa[($value >> 6) & 0x3f]; | |
230 | if ($ictr++ >= $count) { | |
231 | break; | |
232 | } | |
233 | if ($ictr < $count) { | |
234 | $value |= ord($input[$ictr]) << 16; | |
235 | } | |
236 | $output .= static::$itoa[($value >> 12) & 0x3f]; | |
237 | if ($ictr++ < $count) { | |
238 | $output .= static::$itoa[($value >> 18) & 0x3f]; | |
239 | } | |
240 | } while ($ictr < $count); | |
241 | return $output; | |
242 | } | |
243 | ||
244 | } | |
245 | ||
246 | PHPASS::init(); |