Cleanup: Updated copyright comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / classes / salts / class.tx_saltedpasswords_salts_phpass.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009-2011 Marcus Krause <marcus#exp2009@t3sec.info>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains class "tx_saltedpasswords_salts_phpass"
29 * that provides PHPass salted hashing.
30 *
31 * Derived from Drupal CMS
32 * original license: GNU General Public License (GPL)
33 * @see http://drupal.org/node/29706/
34 *
35 * Based on the Portable PHP password hashing framework
36 * original license: Public Domain
37 * @see http://www.openwall.com/phpass/
38 *
39 * $Id$
40 */
41
42
43 /**
44 * Class that implements PHPass salted hashing based on Drupal's
45 * modified Openwall implementation.
46 *
47 * PHPass should work on every system.
48 *
49 * @author Marcus Krause <marcus#exp2009@t3sec.info>
50 *
51 * @since 2009-09-06
52 * @package TYPO3
53 * @subpackage tx_saltedpasswords
54 */
55 class tx_saltedpasswords_salts_phpass extends tx_saltedpasswords_abstract_salts implements tx_saltedpasswords_salts {
56 /**
57 * Keeps a string for mapping an int to the corresponding
58 * base 64 character.
59 */
60 const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
61
62 /**
63 * The default log2 number of iterations for password stretching.
64 */
65 const HASH_COUNT = 14;
66
67 /**
68 * The default maximum allowed log2 number of iterations for
69 * password stretching.
70 */
71 const MAX_HASH_COUNT = 24;
72
73 /**
74 * The default minimum allowed log2 number of iterations for
75 * password stretching.
76 */
77 const MIN_HASH_COUNT = 7;
78
79
80 /**
81 * Keeps log2 number
82 * of iterations for password stretching.
83 *
84 * @var integer
85 */
86 static protected $hashCount;
87
88 /**
89 * Keeps maximum allowed log2 number
90 * of iterations for password stretching.
91 *
92 * @var integer
93 */
94 static protected $maxHashCount;
95
96 /**
97 * Keeps minimum allowed log2 number
98 * of iterations for password stretching.
99 *
100 * @var integer
101 */
102 static protected $minHashCount;
103
104 /**
105 * Keeps length of a PHPass salt in bytes.
106 *
107 * @var integer
108 */
109 static protected $saltLengthPhpass = 6;
110
111 /**
112 * Setting string to indicate type of hashing method (PHPass).
113 *
114 * @var string
115 */
116 static protected $settingPhpass = '$P$';
117
118
119 /**
120 * Method applies settings (prefix, hash count) to a salt.
121 *
122 * Overwrites {@link tx_saltedpasswords_salts_md5::applySettingsToSalt()}
123 * with Blowfish specifics.
124 *
125 * @param string $salt: a salt to apply setting to
126 * @return string salt with setting
127 */
128 protected function applySettingsToSalt($salt) {
129 $saltWithSettings = $salt;
130
131 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
132
133 // salt without setting
134 if (strlen($salt) == $reqLenBase64) {
135 // We encode the final log2 iteration count in base 64.
136 $itoa64 = $this->getItoa64();
137 $saltWithSettings = $this->getSetting() . $itoa64[$this->getHashCount()];
138
139 $saltWithSettings .= $salt;
140 }
141
142 return $saltWithSettings;
143 }
144
145 /**
146 * Method checks if a given plaintext password is correct by comparing it with
147 * a given salted hashed password.
148 *
149 * @param string $plainPW: plain-text password to compare with salted hash
150 * @param string $saltedHashPW: salted hash to compare plain-text password with
151 * @return boolean TRUE, if plain-text password matches the salted hash, otherwise FALSE
152 */
153 public function checkPassword($plainPW, $saltedHashPW) {
154 $hash = $this->cryptPassword($plainPW, $saltedHashPW);
155
156 return ($hash && $saltedHashPW === $hash);
157 }
158
159 /**
160 * Returns wether all prequesites for the hashing methods are matched
161 *
162 * @return boolean method available
163 */
164 public function isAvailable() {
165 return TRUE;
166 }
167
168 /**
169 * Hashes a password using a secure stretched hash.
170 *
171 * By using a salt and repeated hashing the password is "stretched". Its
172 * security is increased because it becomes much more computationally costly
173 * for an attacker to try to break the hash by brute-force computation of the
174 * hashes of a large number of plain-text words or strings to find a match.
175 *
176 * @param string $password: plain-text password to hash
177 * @param string $setting: an existing hash or the output of getGeneratedSalt()
178 * @return mixed a string containing the hashed password (and salt)
179 * or boolean FALSE on failure.
180 */
181 protected function cryptPassword($password, $setting) {
182 $saltedPW = NULL;
183
184 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
185
186 // Retrieving settings with salt
187 $setting = substr($setting, 0, strlen($this->getSetting()) + 1 + $reqLenBase64);
188
189 $count_log2 = $this->getCountLog2($setting);
190
191 // Hashes may be imported from elsewhere, so we allow != HASH_COUNT
192 if ($count_log2 >= $this->getMinHashCount() && $count_log2 <= $this->getMaxHashCount()) {
193
194 $salt = substr($setting, strlen($this->getSetting()) + 1, $reqLenBase64);
195
196 // We must use md5() or sha1() here since they are the only cryptographic
197 // primitives always available in PHP 5. To implement our own low-level
198 // cryptographic function in PHP would result in much worse performance and
199 // consequently in lower iteration counts and hashes that are quicker to crack
200 // (by non-PHP code).
201 $count = 1 << $count_log2;
202
203 $hash = md5($salt . $password, TRUE);
204 do {
205 $hash = md5($hash . $password, TRUE);
206 } while (--$count);
207
208 $saltedPW = $setting . $this->base64Encode($hash, 16);
209
210 // base64Encode() of a 16 byte MD5 will always be 22 characters.
211 return (strlen($saltedPW) == 34) ? $saltedPW : FALSE;
212 }
213
214 return $saltedPW;
215 }
216
217 /**
218 * Parses the log2 iteration count from a stored hash or setting string.
219 *
220 * @param string $setting: complete hash or a hash's setting string or to get log2 iteration count from
221 * @return int used hashcount for given hash string
222 */
223 protected function getCountLog2($setting) {
224 return strpos(
225 $this->getItoa64(),
226 $setting[strlen($this->getSetting())]
227 );
228 }
229
230 /**
231 * Generates a random base 64-encoded salt prefixed and suffixed with settings for the hash.
232 *
233 * Proper use of salts may defeat a number of attacks, including:
234 * - The ability to try candidate passwords against multiple hashes at once.
235 * - The ability to use pre-hashed lists of candidate passwords.
236 * - The ability to determine whether two users have the same (or different)
237 * password without actually having to guess one of the passwords.
238 *
239 * @return string a character string containing settings and a random salt
240 */
241 protected function getGeneratedSalt() {
242 $randomBytes = t3lib_div::generateRandomBytes($this->getSaltLength());
243
244 return $this->base64Encode($randomBytes, $this->getSaltLength());
245 }
246
247 /**
248 * Method returns log2 number of iterations for password stretching.
249 *
250 * @return integer log2 number of iterations for password stretching
251 * @see HASH_COUNT
252 * @see $hashCount
253 * @see setHashCount()
254 */
255 public function getHashCount() {
256 return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
257 }
258
259 /**
260 * Method creates a salted hash for a given plaintext password
261 *
262 * @param string $password: plaintext password to create a salted hash from
263 * @param string $salt: optional custom salt with setting to use
264 * @return string salted hashed password
265 */
266 public function getHashedPassword($password, $salt = NULL) {
267 $saltedPW = NULL;
268
269 if (!empty($password)) {
270 if (empty($salt) || !$this->isValidSalt($salt)) {
271 $salt = $this->getGeneratedSalt();
272 }
273 $saltedPW = $this->cryptPassword($password, $this->applySettingsToSalt($salt));
274 }
275
276 return $saltedPW;
277 }
278
279 /**
280 * Returns a string for mapping an int to the corresponding base 64 character.
281 *
282 * @return string string for mapping an int to the corresponding base 64 character
283 */
284 protected function getItoa64() {
285 return self::ITOA64;
286 }
287
288 /**
289 * Method returns maximum allowed log2 number of iterations for password stretching.
290 *
291 * @return integer maximum allowed log2 number of iterations for password stretching
292 * @see MAX_HASH_COUNT
293 * @see $maxHashCount
294 * @see setMaxHashCount()
295 */
296 public function getMaxHashCount() {
297 return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
298 }
299
300 /**
301 * Method returns minimum allowed log2 number of iterations for password stretching.
302 *
303 * @return integer minimum allowed log2 number of iterations for password stretching
304 * @see MIN_HASH_COUNT
305 * @see $minHashCount
306 * @see setMinHashCount()
307 */
308 public function getMinHashCount() {
309 return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
310 }
311
312 /**
313 * Returns length of a Blowfish salt in bytes.
314 *
315 * @return integer length of a Blowfish salt in bytes
316 */
317 public function getSaltLength() {
318 return self::$saltLengthPhpass;
319 }
320
321 /**
322 * Returns setting string of PHPass salted hashes.
323 *
324 * @return string setting string of PHPass salted hashes
325 */
326 public function getSetting() {
327 return self::$settingPhpass;
328 }
329
330 /**
331 * Checks whether a user's hashed password needs to be replaced with a new hash.
332 *
333 * This is typically called during the login process when the plain text
334 * password is available. A new hash is needed when the desired iteration
335 * count has changed through a change in the variable $hashCount or
336 * HASH_COUNT or if the user's password hash was generated in an bulk update
337 * with class ext_update.
338 *
339 * @param string $passString salted hash to check if it needs an update
340 * @return boolean TRUE if salted hash needs an update, otherwise FALSE
341 */
342 public function isHashUpdateNeeded($passString) {
343 // Check whether this was an updated password.
344 if ((strncmp($passString, '$P$', 3)) || (strlen($passString) != 34)) {
345 return TRUE;
346 }
347 // Check whether the iteration count used differs from the standard number.
348 return ($this->getCountLog2($passString) < $this->getHashCount());
349 }
350
351 /**
352 * Method determines if a given string is a valid salt.
353 *
354 * @param string $salt: string to check
355 * @return boolean TRUE if it's valid salt, otherwise FALSE
356 */
357 public function isValidSalt($salt) {
358 $isValid = $skip = FALSE;
359
360 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
361
362 if (strlen($salt) >= $reqLenBase64) {
363 // salt with prefixed setting
364 if (!strncmp('$', $salt, 1)) {
365 if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
366 $isValid = TRUE;
367 $salt = substr($salt, strrpos($salt, '$') + 2);
368 } else {
369 $skip = TRUE;
370 }
371 }
372
373 // checking base64 characters
374 if (!$skip && (strlen($salt) >= $reqLenBase64)) {
375 if (preg_match('/^[' . preg_quote($this->getItoa64(),'/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
376 $isValid = TRUE;
377 }
378 }
379 }
380
381 return $isValid;
382 }
383
384 /**
385 * Method determines if a given string is a valid salted hashed password.
386 *
387 * @param string $saltedPW: string to check
388 * @return boolean TRUE if it's valid salted hashed password, otherwise FALSE
389 */
390 public function isValidSaltedPW($saltedPW) {
391 $isValid = FALSE;
392
393 $isValid = (!strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()))) ? TRUE : FALSE;
394 if ($isValid) {
395 $isValid = $this->isValidSalt($saltedPW);
396 }
397
398 return $isValid;
399 }
400
401 /**
402 * Method sets log2 number of iterations for password stretching.
403 *
404 * @param integer $hashCount: log2 number of iterations for password stretching to set
405 * @see HASH_COUNT
406 * @see $hashCount
407 * @see getHashCount()
408 */
409 public function setHashCount($hashCount = NULL) {
410 self::$hashCount = !is_NULL($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
411 }
412
413 /**
414 * Method sets maximum allowed log2 number of iterations for password stretching.
415 *
416 * @param integer $maxHashCount: maximum allowed log2 number of iterations for password stretching to set
417 * @see MAX_HASH_COUNT
418 * @see $maxHashCount
419 * @see getMaxHashCount()
420 */
421 public function setMaxHashCount($maxHashCount = NULL) {
422 self::$maxHashCount = !is_NULL($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
423 }
424
425 /**
426 * Method sets minimum allowed log2 number of iterations for password stretching.
427 *
428 * @param integer $minHashCount minimum allowed log2 number of iterations for password stretching to set
429 * @see MIN_HASH_COUNT
430 * @see $minHashCount
431 * @see getMinHashCount()
432 */
433 public function setMinHashCount($minHashCount = NULL) {
434 self::$minHashCount = !is_NULL($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
435 }
436 }
437
438
439 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/salts/class.tx_saltedpasswords_salts_phpass.php'])) {
440 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/salts/class.tx_saltedpasswords_salts_phpass.php']);
441 }
442 ?>