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