* Provides functions to compute password hashes.
*
* @author Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Util
*/
* @var string[]
*/
private static $supportedEncryptionTypes = [
+ 'argon2', // vBulletin 5.x
'ipb2', // Invision Power Board 2.x
'ipb3', // Invision Power Board 3.x
'mybb1', // MyBB 1.x
* @deprecated Use \wcf\util\CryptoUtil::secureCompare()
*/
public static function secureCompare($hash1, $hash2) {
- return CryptoUtil::secureCompare($hash1, $hash2);
+ return \hash_equals($hash1, $hash2);
}
/**
- * Generates secure random numbers using OpenSSL.
- *
- * @see http://de1.php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322
- * @param integer $min
- * @param integer $max
- * @return integer
- * @throws SystemException
+ * @deprecated Use random_int()
*/
public static function secureRandomNumber($min, $max) {
$range = $max - $min;
}
/**
- * Validates the password hash for Invision Power Board 2.x (ipb2).
+ * Validates the password hash for Argon2 (argon2).
*
* @param string $username
* @param string $password
* @param string $dbHash
* @return boolean
*/
- protected static function ipb2($username, $password, $salt, $dbHash) {
- return self::vb3($username, $password, $salt, $dbHash);
+ protected static function argon2($username, $password, $salt, $dbHash) {
+ return password_verify($password, $dbHash);
}
/**
- * Validates the password hash for Invision Power Board 3.x (ipb3).
+ * Validates the password hash for Invision Power Board 2.x (ipb2).
*
* @param string $username
* @param string $password
* @param string $dbHash
* @return boolean
*/
- protected static function ipb3($username, $password, $salt, $dbHash) {
- return CryptoUtil::secureCompare($dbHash, md5(md5($salt) . md5($password)));
+ protected static function ipb2($username, $password, $salt, $dbHash) {
+ return self::vb3($username, $password, $salt, $dbHash);
}
/**
- * Validates the password hash for MyBB 1.x (mybb1).
+ * Validates the password hash for Invision Power Board 3.x (ipb3).
*
* @param string $username
* @param string $password
* @param string $dbHash
* @return boolean
*/
- protected static function mybb1($username, $password, $salt, $dbHash) {
- return CryptoUtil::secureCompare($dbHash, md5(md5($salt) . md5($password)));
+ protected static function ipb3($username, $password, $salt, $dbHash) {
+ return \hash_equals($dbHash, md5(md5($salt) . md5($password)));
}
+
/**
- * Validates the password hash for phpBB 3.x (phpbb3).
+ * Validates the password hash for MyBB 1.x (mybb1).
*
* @param string $username
* @param string $password
* @param string $dbHash
* @return boolean
*/
- protected static function phpbb3($username, $password, $salt, $dbHash) {
- return self::phpass($username, $password, $salt, $dbHash);
+ protected static function mybb1($username, $password, $salt, $dbHash) {
+ return \hash_equals($dbHash, md5(md5($salt) . md5($password)));
}
-
/**
- * Validates the password hash for phpass portable hashes (phpass).
+ * Validates the password hash for phpBB 3.x (phpbb3).
*
* @param string $username
* @param string $password
* @param string $dbHash
* @return boolean
*/
- protected static function phpass($username, $password, $salt, $dbHash) {
- if (mb_strlen($dbHash) !== 34) {
- return CryptoUtil::secureCompare(md5($password), $dbHash);
+ public static function phpbb3($username, $password, $salt, $dbHash) {
+ $phpassResult = self::phpass($username, $password, $salt, $dbHash);
+
+ if ($phpassResult) {
+ return true;
}
- $hash_crypt_private = function ($password, $setting) {
- static $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
-
- $output = '*';
-
- // Check for correct hash
- if (substr($setting, 0, 3) !== '$H$' && substr($setting, 0, 3) !== '$P$') {
- return $output;
- }
-
- $count_log2 = strpos($itoa64, $setting[3]);
-
- if ($count_log2 < 7 || $count_log2 > 30) {
- return $output;
+ if (!preg_match('/^\$([^$]+)\$/', $dbHash, $matches)) {
+ return false;
+ }
+
+ $algorithms = explode('\\', $matches[1]);
+ // Strip the type prefix.
+ $dbHash = substr($dbHash, strlen($matches[0]));
+
+ // The following loop only supports the multi-hash variant.
+ // Everything else should already be handled at this point.
+ if (count($algorithms) == 1) {
+ return false;
+ }
+ foreach ($algorithms as $algorithm) {
+ $dollar = strpos($dbHash, '$');
+ if ($dollar === false) {
+ return false;
}
+ $settings = '$'.$algorithm.'$'.str_replace('\\', '$', substr($dbHash, 0, $dollar));
+ $dbHash = substr($dbHash, $dollar + 1);
- $count = 1 << $count_log2;
- $salt = substr($setting, 4, 8);
-
- if (strlen($salt) != 8) {
- return $output;
+ switch ($algorithm) {
+ case 'H':
+ case 'P':
+ $password = str_replace($settings, '', self::phpass_hash_crypt_private($password, $settings));
+ break;
+ case '2a':
+ case '2y':
+ $password = str_replace($settings, '', self::getSaltedHash($password, $settings));
+ break;
}
+ }
+
+ return \hash_equals($dbHash, $password);
+ }
+
+ private static function phpass_hash_crypt_private($password, $setting) {
+ static $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+ $output = '*';
+
+ // Check for correct hash
+ if (substr($setting, 0, 3) !== '$H$' && substr($setting, 0, 3) !== '$P$') {
+ return $output;
+ }
+
+ $count_log2 = strpos($itoa64, $setting[3]);
+
+ if ($count_log2 < 7 || $count_log2 > 30) {
+ return $output;
+ }
+
+ $count = 1 << $count_log2;
+ $salt = substr($setting, 4, 8);
+
+ if (strlen($salt) != 8) {
+ return $output;
+ }
+
+ $hash = md5($salt . $password, true);
+ do {
+ $hash = md5($hash . $password, true);
+ }
+ while (--$count);
+
+ $output = substr($setting, 0, 12);
+ $hash_encode64 = function ($input, $count, &$itoa64) {
+ $output = '';
+ $i = 0;
- $hash = md5($salt . $password, true);
do {
- $hash = md5($hash . $password, true);
- }
- while (--$count);
-
- $output = substr($setting, 0, 12);
- $hash_encode64 = function ($input, $count, &$itoa64) {
- $output = '';
- $i = 0;
+ $value = ord($input[$i++]);
+ $output .= $itoa64[$value & 0x3f];
- do {
- $value = ord($input[$i++]);
- $output .= $itoa64[$value & 0x3f];
-
- if ($i < $count) {
- $value |= ord($input[$i]) << 8;
- }
-
- $output .= $itoa64[($value >> 6) & 0x3f];
-
- if ($i++ >= $count) {
- break;
- }
-
- if ($i < $count) {
- $value |= ord($input[$i]) << 16;
- }
-
- $output .= $itoa64[($value >> 12) & 0x3f];
-
- if ($i++ >= $count) {
- break;
- }
-
- $output .= $itoa64[($value >> 18) & 0x3f];
+ if ($i < $count) {
+ $value |= ord($input[$i]) << 8;
}
- while ($i < $count);
- return $output;
- };
-
- $output .= $hash_encode64($hash, 16, $itoa64);
+ $output .= $itoa64[($value >> 6) & 0x3f];
+
+ if ($i++ >= $count) {
+ break;
+ }
+
+ if ($i < $count) {
+ $value |= ord($input[$i]) << 16;
+ }
+
+ $output .= $itoa64[($value >> 12) & 0x3f];
+
+ if ($i++ >= $count) {
+ break;
+ }
+
+ $output .= $itoa64[($value >> 18) & 0x3f];
+ }
+ while ($i < $count);
return $output;
};
- return CryptoUtil::secureCompare($hash_crypt_private($password, $dbHash), $dbHash);
+ $output .= $hash_encode64($hash, 16, $itoa64);
+
+ return $output;
+ }
+
+ /**
+ * Validates the password hash for phpass portable hashes (phpass).
+ *
+ * @param string $username
+ * @param string $password
+ * @param string $salt
+ * @param string $dbHash
+ * @return boolean
+ */
+ protected static function phpass($username, $password, $salt, $dbHash) {
+ if (mb_strlen($dbHash) !== 34) {
+ return \hash_equals(md5($password), $dbHash);
+ }
+
+ return \hash_equals(self::phpass_hash_crypt_private($password, $dbHash), $dbHash);
}
/**
* @return boolean
*/
protected static function smf1($username, $password, $salt, $dbHash) {
- return CryptoUtil::secureCompare($dbHash, sha1(mb_strtolower($username) . $password));
+ return \hash_equals($dbHash, sha1(mb_strtolower($username) . $password));
}
/**
* @return boolean
*/
protected static function vb3($username, $password, $salt, $dbHash) {
- return CryptoUtil::secureCompare($dbHash, md5(md5($password) . $salt));
+ return \hash_equals($dbHash, md5(md5($password) . $salt));
}
/**
* @return boolean
*/
protected static function wbb2($username, $password, $salt, $dbHash) {
- if (CryptoUtil::secureCompare($dbHash, md5($password))) {
+ if (\hash_equals($dbHash, md5($password))) {
return true;
}
- else if (CryptoUtil::secureCompare($dbHash, sha1($password))) {
+ else if (\hash_equals($dbHash, sha1($password))) {
return true;
}
* @return boolean
*/
protected static function wcf1($username, $password, $salt, $dbHash) {
- return CryptoUtil::secureCompare($dbHash, sha1($salt . sha1($salt . sha1($password))));
+ return \hash_equals($dbHash, sha1($salt . sha1($salt . sha1($password))));
}
/**
}
$hash = $encryptionMethod($salt . $hash);
- return CryptoUtil::secureCompare($dbHash, $hash);
+ return \hash_equals($dbHash, $hash);
}
/**
* @return boolean
*/
protected static function wcf2($username, $password, $salt, $dbHash) {
- return CryptoUtil::secureCompare($dbHash, self::getDoubleSaltedHash($password, $dbHash));
+ return \hash_equals($dbHash, self::getDoubleSaltedHash($password, $dbHash));
}
/**
* @return boolean
*/
protected static function xf1($username, $password, $salt, $dbHash) {
- if (CryptoUtil::secureCompare($dbHash, sha1(sha1($password) . $salt))) {
+ if (\hash_equals($dbHash, sha1(sha1($password) . $salt))) {
return true;
}
- return CryptoUtil::secureCompare($dbHash, hash('sha256', hash('sha256', $password) . $salt));
+ return \hash_equals($dbHash, hash('sha256', hash('sha256', $password) . $salt));
}
/**
* @return boolean
*/
protected static function xf12($username, $password, $salt, $dbHash) {
- if (CryptoUtil::secureCompare($dbHash, self::getSaltedHash($password, $dbHash))) {
+ if (\hash_equals($dbHash, self::getSaltedHash($password, $dbHash))) {
return true;
}
* @return boolean
*/
protected static function joomla1($username, $password, $salt, $dbHash) {
- if (CryptoUtil::secureCompare($dbHash, md5($password . $salt))) {
+ if (\hash_equals($dbHash, md5($password . $salt))) {
return true;
}
* @return boolean
*/
protected static function phpfox3($username, $password, $salt, $dbHash) {
- if (CryptoUtil::secureCompare($dbHash, md5(md5($password) . md5($salt)))) {
+ if (\hash_equals($dbHash, md5(md5($password) . md5($salt)))) {
return true;
}
* @return boolean
*/
protected static function cryptMD5($username, $password, $salt, $dbHash) {
- if (CryptoUtil::secureCompare($dbHash, self::getSaltedHash($password, $dbHash))) {
+ if (\hash_equals($dbHash, self::getSaltedHash($password, $dbHash))) {
return true;
}