TYPO3 CMS  TYPO3_8-7
PhpassSalt.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 
31 class PhpassSalt extends AbstractSalt implements SaltInterface
32 {
37  const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
38 
42  const HASH_COUNT = 14;
43 
48  const MAX_HASH_COUNT = 24;
49 
54  const MIN_HASH_COUNT = 7;
55 
62  protected static $hashCount;
63 
70  protected static $maxHashCount;
71 
78  protected static $minHashCount;
79 
85  protected static $saltLengthPhpass = 6;
86 
92  protected static $settingPhpass = '$P$';
93 
103  protected function applySettingsToSalt($salt)
104  {
105  $saltWithSettings = $salt;
106  $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
107  // Salt without setting
108  if (strlen($salt) == $reqLenBase64) {
109  // We encode the final log2 iteration count in base 64.
110  $itoa64 = $this->getItoa64();
111  $saltWithSettings = $this->getSetting() . $itoa64[$this->getHashCount()];
112  $saltWithSettings .= $salt;
113  }
114  return $saltWithSettings;
115  }
116 
125  public function checkPassword($plainPW, $saltedHashPW)
126  {
127  $hash = $this->cryptPassword($plainPW, $saltedHashPW);
128  return $hash && hash_equals($hash, $saltedHashPW);
129  }
130 
136  public function isAvailable()
137  {
138  return true;
139  }
140 
153  protected function cryptPassword($password, $setting)
154  {
155  $saltedPW = null;
156  $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
157  // Retrieving settings with salt
158  $setting = substr($setting, 0, strlen($this->getSetting()) + 1 + $reqLenBase64);
159  $count_log2 = $this->getCountLog2($setting);
160  // Hashes may be imported from elsewhere, so we allow != HASH_COUNT
161  if ($count_log2 >= $this->getMinHashCount() && $count_log2 <= $this->getMaxHashCount()) {
162  $salt = substr($setting, strlen($this->getSetting()) + 1, $reqLenBase64);
163  // We must use md5() or sha1() here since they are the only cryptographic
164  // primitives always available in PHP 5. To implement our own low-level
165  // cryptographic function in PHP would result in much worse performance and
166  // consequently in lower iteration counts and hashes that are quicker to crack
167  // (by non-PHP code).
168  $count = 1 << $count_log2;
169  $hash = md5($salt . $password, true);
170  do {
171  $hash = md5($hash . $password, true);
172  } while (--$count);
173  $saltedPW = $setting . $this->base64Encode($hash, 16);
174  // base64Encode() of a 16 byte MD5 will always be 22 characters.
175  return strlen($saltedPW) == 34 ? $saltedPW : false;
176  }
177  return $saltedPW;
178  }
179 
186  protected function getCountLog2($setting)
187  {
188  return strpos($this->getItoa64(), $setting[strlen($this->getSetting())]);
189  }
190 
202  protected function getGeneratedSalt()
203  {
204  $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->getSaltLength());
205  return $this->base64Encode($randomBytes, $this->getSaltLength());
206  }
207 
216  public function getHashCount()
217  {
218  return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
219  }
220 
228  public function getHashedPassword($password, $salt = null)
229  {
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 
245  protected function getItoa64()
246  {
247  return self::ITOA64;
248  }
249 
258  public function getMaxHashCount()
259  {
260  return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
261  }
262 
271  public function getMinHashCount()
272  {
273  return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
274  }
275 
281  public function getSaltLength()
282  {
283  return self::$saltLengthPhpass;
284  }
285 
291  public function getSetting()
292  {
293  return self::$settingPhpass;
294  }
295 
308  public function isHashUpdateNeeded($passString)
309  {
310  // Check whether this was an updated password.
311  if (strncmp($passString, '$P$', 3) || strlen($passString) != 34) {
312  return true;
313  }
314  // Check whether the iteration count used differs from the standard number.
315  return $this->getCountLog2($passString) < $this->getHashCount();
316  }
317 
324  public function isValidSalt($salt)
325  {
326  $isValid = ($skip = false);
327  $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
328  if (strlen($salt) >= $reqLenBase64) {
329  // Salt with prefixed setting
330  if (!strncmp('$', $salt, 1)) {
331  if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
332  $isValid = true;
333  $salt = substr($salt, strrpos($salt, '$') + 2);
334  } else {
335  $skip = true;
336  }
337  }
338  // Checking base64 characters
339  if (!$skip && strlen($salt) >= $reqLenBase64) {
340  if (preg_match('/^[' . preg_quote($this->getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
341  $isValid = true;
342  }
343  }
344  }
345  return $isValid;
346  }
347 
354  public function isValidSaltedPW($saltedPW)
355  {
356  $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
357  if ($isValid) {
358  $isValid = $this->isValidSalt($saltedPW);
359  }
360  return $isValid;
361  }
362 
371  public function setHashCount($hashCount = null)
372  {
373  self::$hashCount = !is_null($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
374  }
375 
384  public function setMaxHashCount($maxHashCount = null)
385  {
386  self::$maxHashCount = !is_null($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
387  }
388 
397  public function setMinHashCount($minHashCount = null)
398  {
399  self::$minHashCount = !is_null($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
400  }
401 }
static makeInstance($className,... $constructorArguments)
getHashedPassword($password, $salt=null)
Definition: PhpassSalt.php:228
checkPassword($plainPW, $saltedHashPW)
Definition: PhpassSalt.php:125