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