diff --git a/Sources/LogInOut.php b/Sources/LogInOut.php
index 6816300..4397c48 100644
--- a/Sources/LogInOut.php
+++ b/Sources/LogInOut.php
@@ -258,131 +258,33 @@ function Login2()
$user_settings = $smcFunc['db_fetch_assoc']($request);
$smcFunc['db_free_result']($request);
+ // Load scrypt stuff.
+ require_once($sourcedir . '/scrypt.php');
+
// Figure out the password using SMF's encryption - if what they typed is right.
if (isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40)
{
- // Needs upgrading?
- if (strlen($user_settings['passwd']) != 40)
- {
+ $sha_passwd = $_POST['hash_passwrd'];
+
+ // default SMF checked to see here if $user_settings['passwd'] was sha1 and disabled javascript hashing if not, like so:
+ /*
$context['login_errors'] = array($txt['login_hash_error']);
$context['disable_login_hashing'] = true;
unset($user_settings);
return;
- }
- // Challenge passed.
- elseif ($_POST['hash_passwrd'] == sha1($user_settings['passwd'] . $sc))
- $sha_passwd = $user_settings['passwd'];
- else
- {
- // Don't allow this!
- validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']);
-
- $_SESSION['failed_login'] = @$_SESSION['failed_login'] + 1;
-
- if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold'])
- redirectexit('action=reminder');
- else
- {
- log_error($txt['incorrect_password'] . ' - ' . $user_settings['member_name'] . '', 'user');
-
- $context['disable_login_hashing'] = true;
- $context['login_errors'] = array($txt['incorrect_password']);
- unset($user_settings);
- return;
- }
- }
+ */
+ // I'm leaving this off though, I converted whole db to scrypt, so we always want javascript to hash!
}
else
$sha_passwd = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
+
// Bad password! Thought you could fool the database?!
- if ($user_settings['passwd'] != $sha_passwd)
+ if (!Password::check($sha_passwd, $user_settings['passwd']))
{
// Let's be cautious, no hacking please. thanx.
validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']);
- // Maybe we were too hasty... let's try some other authentication methods.
- $other_passwords = array();
-
- // None of the below cases will be used most of the time (because the salt is normally set.)
- if ($user_settings['password_salt'] == '')
- {
- // YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, IkonBoard, and none at all.
- $other_passwords[] = crypt($_POST['passwrd'], substr($_POST['passwrd'], 0, 2));
- $other_passwords[] = crypt($_POST['passwrd'], substr($user_settings['passwd'], 0, 2));
- $other_passwords[] = md5($_POST['passwrd']);
- $other_passwords[] = sha1($_POST['passwrd']);
- $other_passwords[] = md5_hmac($_POST['passwrd'], strtolower($user_settings['member_name']));
- $other_passwords[] = md5($_POST['passwrd'] . strtolower($user_settings['member_name']));
- $other_passwords[] = md5(md5($_POST['passwrd']));
- $other_passwords[] = $_POST['passwrd'];
-
- // This one is a strange one... MyPHP, crypt() on the MD5 hash.
- $other_passwords[] = crypt(md5($_POST['passwrd']), md5($_POST['passwrd']));
-
- // Snitz style - SHA-256. Technically, this is a downgrade, but most PHP configurations don't support sha256 anyway.
- if (strlen($user_settings['passwd']) == 64 && function_exists('mhash') && defined('MHASH_SHA256'))
- $other_passwords[] = bin2hex(mhash(MHASH_SHA256, $_POST['passwrd']));
-
- // phpBB3 users new hashing. We now support it as well ;).
- $other_passwords[] = phpBB3_password_check($_POST['passwrd'], $user_settings['passwd']);
-
- // APBoard 2 Login Method.
- $other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5'));
- }
- // The hash should be 40 if it's SHA-1, so we're safe with more here too.
- elseif (strlen($user_settings['passwd']) == 32)
- {
- // vBulletin 3 style hashing? Let's welcome them with open arms \o/.
- $other_passwords[] = md5(md5($_POST['passwrd']) . $user_settings['password_salt']);
-
- // Hmm.. p'raps it's Invision 2 style?
- $other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
-
- // Some common md5 ones.
- $other_passwords[] = md5($user_settings['password_salt'] . $_POST['passwrd']);
- $other_passwords[] = md5($_POST['passwrd'] . $user_settings['password_salt']);
- }
- elseif (strlen($user_settings['passwd']) == 40)
- {
- // Maybe they are using a hash from before the password fix.
- $other_passwords[] = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
-
- // BurningBoard3 style of hashing.
- $other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd'])));
-
- // Perhaps we converted to UTF-8 and have a valid password being hashed differently.
- if ($context['character_set'] == 'utf8' && !empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] != 'utf8')
- {
- // Try iconv first, for no particular reason.
- if (function_exists('iconv'))
- $other_passwords['iconv'] = sha1(strtolower(iconv('UTF-8', $modSettings['previousCharacterSet'], $user_settings['member_name'])) . un_htmlspecialchars(iconv('UTF-8', $modSettings['previousCharacterSet'], $_POST['passwrd'])));
-
- // Say it aint so, iconv failed!
- if (empty($other_passwords['iconv']) && function_exists('mb_convert_encoding'))
- $other_passwords[] = sha1(strtolower(mb_convert_encoding($user_settings['member_name'], 'UTF-8', $modSettings['previousCharacterSet'])) . un_htmlspecialchars(mb_convert_encoding($_POST['passwrd'], 'UTF-8', $modSettings['previousCharacterSet'])));
- }
- }
-
- // SMF's sha1 function can give a funny result on Linux (Not our fault!). If we've now got the real one let the old one be valid!
- if (strpos(strtolower(PHP_OS), 'win') !== 0)
- {
- require_once($sourcedir . '/Subs-Compat.php');
- $other_passwords[] = sha1_smf(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
- }
-
- // Whichever encryption it was using, let's make it use SMF's now ;).
- if (in_array($user_settings['passwd'], $other_passwords))
- {
- $user_settings['passwd'] = $sha_passwd;
- $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4);
-
- // Update the password and set up the hash.
- updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'], 'passwd_flood' => ''));
- }
- // Okay, they for sure didn't enter the password!
- else
- {
// They've messed up again - keep a count to see if they need a hand.
$_SESSION['failed_login'] = @$_SESSION['failed_login'] + 1;
@@ -398,7 +300,6 @@ function Login2()
$context['login_errors'] = array($txt['incorrect_password']);
return;
}
- }
}
elseif (!empty($user_settings['passwd_flood']))
{
diff --git a/Sources/Profile-Modify.php b/Sources/Profile-Modify.php
index 565d230..99febac 100644
--- a/Sources/Profile-Modify.php
+++ b/Sources/Profile-Modify.php
@@ -516,6 +516,10 @@ function loadProfileFields($force_reload = false)
// Set up the new password variable... ready for storage.
$value = sha1(strtolower($cur_profile[\'member_name\']) . un_htmlspecialchars($value));
+
+ require_once($sourcedir . \'/scrypt.php\');
+ $value = Password::hash($value);
+
return true;
'),
),
@@ -618,7 +622,8 @@ function loadProfileFields($force_reload = false)
'value' => '',
'permission' => 'profile_identity',
'input_validate' => create_function('&$value', '
- $value = $value != \'\' ? md5($value) : \'\';
+ require_once($sourcedir . \'/scrypt.php\');
+ $value = $value != \'\' ? Password::hash(md5($value)) : \'\';
return true;
'),
),
@@ -1364,14 +1369,20 @@ function makeCustomFieldChanges($memID, $area, $sanitize = true)
//echo "success!"; exit;
}
- if($area != 'register' && ($row['col_name'] == "cust_moparcr") && $value != '' && strlen($value) != 40 && (!isset($user_profile[$memID]['options'][$row['col_name']]) || $user_profile[$memID]['options'][$row['col_name']] != $value)){
+ if($area != 'register' && ($row['col_name'] == "cust_moparcr") && $value != '' && strlen($value) < 31 && (!isset($user_profile[$memID]['options'][$row['col_name']]) || $user_profile[$memID]['options'][$row['col_name']] != $value)){
//print_r($user_info);echo("--------------------------------------------------------------\n");print_r($user_profile);exit;
//die(strlen($value)."bob");
if(strlen($value) > 30)
die("Error: Maximum length for MoparCraft server password is 30 characters.");
$value = sha1(strtolower($user_profile[$memID]['member_name']) . htmlspecialchars_decode($value));
- if($user_info['id'] == $memID && $value == $user_info['passwd'])
+
+ global $sourcedir;
+ require_once($sourcedir . '/scrypt.php');
+
+ if($user_info['id'] == $memID && Password::check($value, $user_info['passwd']))
die("Error: You can't set your MoparCraft server password to be the same as your forum password, if you want to use your forum password, leave this blank.");
+
+ $value = Password::hash($value);
}
//xxx end
}
@@ -1862,10 +1873,17 @@ function authentication($memID, $saving = false)
// Go then.
$passwd = sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd1']));
+ require_once($sourcedir . '/scrypt.php');
+ $passwd = Password::hash($passwd);
+
// Do the important bits.
updateMemberData($memID, array('openid_uri' => '', 'passwd' => $passwd));
- if ($context['user']['is_owner'])
- setLoginCookie(60 * $modSettings['cookieTime'], $memID, sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt']));
+
+ if ($context['user']['is_owner']) {
+ global $user_settings;
+ $user_settings['passwd'] = $passwd;
+ setLoginCookie(60 * $modSettings['cookieTime'], $memID, sha1($passwd . $cur_profile['password_salt']));
+ }
redirectexit('action=profile;u=' . $memID);
}
@@ -3086,7 +3104,9 @@ function profileReloadUser()
if (isset($_POST['passwrd2']) && $_POST['passwrd2'] != '')
{
require_once($sourcedir . '/Subs-Auth.php');
- setLoginCookie(60 * $modSettings['cookieTime'], $context['id_member'], sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt']));
+
+ global $user_settings;
+ setLoginCookie(60 * $modSettings['cookieTime'], $context['id_member'], sha1($user_settings['passwd'] . $cur_profile['password_salt']));
}
loadUserSettings();
diff --git a/Sources/Profile.php b/Sources/Profile.php
index f70d191..314befb 100644
--- a/Sources/Profile.php
+++ b/Sources/Profile.php
@@ -510,7 +510,8 @@ function ModifyProfile($post_errors = array())
$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($cur_profile['member_name'], $_POST['oldpasswrd'], false)), true);
// Bad password!!!
- if (!$good_password && $user_info['passwd'] != sha1(strtolower($cur_profile['member_name']) . $_POST['oldpasswrd']))
+ require_once($sourcedir . '/scrypt.php');
+ if (!$good_password && !Password::check(sha1(strtolower($cur_profile['member_name']) . $_POST['oldpasswrd']), $user_info['passwd']))
$post_errors[] = 'bad_password';
// Warn other elements not to jump the gun and do custom changes!
diff --git a/Sources/Register.php b/Sources/Register.php
index 5b04cdb..cf504e2 100644
--- a/Sources/Register.php
+++ b/Sources/Register.php
@@ -299,8 +299,11 @@ function Register2($verifiedOpenID = false)
'hide_email', 'show_online',
);
- if (isset($_POST['secret_answer']) && $_POST['secret_answer'] != '')
+ if (isset($_POST['secret_answer']) && $_POST['secret_answer'] != '') {
$_POST['secret_answer'] = md5($_POST['secret_answer']);
+ require_once($sourcedir . '/scrypt.php');
+ $_POST['secret_answer'] = Password::hash($_POST['secret_answer']);
+ }
// Needed for isReservedName() and registerMember().
require_once($sourcedir . '/Subs-Members.php');
@@ -509,6 +512,8 @@ function Register2($verifiedOpenID = false)
if($value == $regOptions['password'])
die("Error: You can't set your MoparCraft server password to be the same as your forum password, if you want to use your forum password, leave this blank.");
$value = sha1(strtolower($regOptions['username']) . htmlspecialchars_decode($value));
+ require_once($sourcedir . '/scrypt.php');
+ $value = Password::hash($value);
$_POST['customfield'][$row['col_name']] = $value;
}
// xxx end if we are editing our minecraft name, make sure there are no duplicates
@@ -594,6 +599,7 @@ function Register2($verifiedOpenID = false)
{
call_integration_hook('integrate_activate', array($row['member_name']));
+ // todo: set scrypt here, but we don't use this reg method so whatever
setLoginCookie(60 * $modSettings['cookieTime'], $memberID, sha1(sha1(strtolower($regOptions['username']) . $regOptions['password']) . $regOptions['register_vars']['password_salt']));
redirectexit('action=login2;sa=check;member=' . $memberID, $context['server']['needs_login_fix']);
@@ -647,8 +653,10 @@ function Activate()
$row = $smcFunc['db_fetch_assoc']($request);
$smcFunc['db_free_result']($request);
+ require_once($sourcedir . '/scrypt.php');
+
// Change their email address? (they probably tried a fake one first :P.)
- if (isset($_POST['new_email'], $_REQUEST['passwd']) && sha1(strtolower($row['member_name']) . $_REQUEST['passwd']) == $row['passwd'] && ($row['is_activated'] == 0 || $row['is_activated'] == 2))
+ if (isset($_POST['new_email'], $_REQUEST['passwd']) && Password::check(sha1(strtolower($row['member_name']) . $_REQUEST['passwd']), $row['passwd']) && ($row['is_activated'] == 0 || $row['is_activated'] == 2))
{
if (empty($modSettings['registration_method']) || $modSettings['registration_method'] == 3)
fatal_lang_error('no_access', false);
@@ -915,8 +923,8 @@ function RegisterCheckUsername()
$context['checked_username'] = $smcFunc['htmltrim']($smcFunc['substr']($context['checked_username'], 0, 25));
//xxx only allow these characters
- if(!RegisterUserIsAllowed($context['checked_username'], "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ /-:.%0123456789_"))
- $context['valid_username'] = false;
+ //if(!RegisterUserIsAllowed($context['checked_username'], "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ /-:.%0123456789_"))
+ // $context['valid_username'] = false;
// Only these characters are permitted.
if (preg_match('~[<>&"\'=\\\]~', preg_replace('~(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $context['checked_username'])) != 0 || $context['checked_username'] == '_' || $context['checked_username'] == '|' || strpos($context['checked_username'], '[code') !== false || strpos($context['checked_username'], '[/code') !== false)
diff --git a/Sources/Reminder.php b/Sources/Reminder.php
index 0f3a57d..27b870b 100644
--- a/Sources/Reminder.php
+++ b/Sources/Reminder.php
@@ -260,7 +260,8 @@ function setPassword2()
validatePasswordFlood($_POST['u'], $flood_value, true);
// User validated. Update the database!
- updateMemberData($_POST['u'], array('validation_code' => '', 'passwd' => sha1(strtolower($username) . $_POST['passwrd1'])));
+ require_once($sourcedir . '/scrypt.php');
+ updateMemberData($_POST['u'], array('validation_code' => '', 'passwd' => Password::hash(sha1(strtolower($username) . $_POST['passwrd1']))));
call_integration_hook('integrate_reset_pass', array($username, $username, $_POST['passwrd1']));
@@ -348,7 +349,8 @@ function SecretAnswer2()
$smcFunc['db_free_result']($request);
// Check if the secret answer is correct.
- if ($row['secret_question'] == '' || $row['secret_answer'] == '' || md5($_POST['secret_answer']) != $row['secret_answer'])
+ require_once($sourcedir . '/scrypt.php');
+ if ($row['secret_question'] == '' || $row['secret_answer'] == '' || !Password::check(md5($_POST['secret_answer']), $row['secret_answer']))
{
log_error(sprintf($txt['reminder_error'], $row['member_name']), 'user');
fatal_lang_error('incorrect_answer', false);
@@ -379,7 +381,8 @@ function SecretAnswer2()
fatal_lang_error('profile_error_password_' . $passwordError, false);
// Alright, so long as 'yer sure.
- updateMemberData($row['id_member'], array('passwd' => sha1(strtolower($row['member_name']) . $_POST['passwrd1'])));
+ require_once($sourcedir . '/scrypt.php');
+ updateMemberData($row['id_member'], array('passwd' => Password::hash(sha1(strtolower($row['member_name']) . $_POST['passwrd1']))));
call_integration_hook('integrate_reset_pass', array($row['member_name'], $row['member_name'], $_POST['passwrd1']));
diff --git a/Sources/Subs-Auth.php b/Sources/Subs-Auth.php
index f1a2dfa..775a85d 100644
--- a/Sources/Subs-Auth.php
+++ b/Sources/Subs-Auth.php
@@ -582,6 +582,9 @@ function resetPassword($memID, $username = null)
$newPassword = substr(preg_replace('/\W/', '', md5(mt_rand())), 0, 10);
$newPassword_sha1 = sha1(strtolower($user) . $newPassword);
+ require_once($sourcedir . '/scrypt.php');
+ $newPassword_sha1 = Password::hash($newPassword_sha1);
+
// Do some checks on the username if needed.
if ($username !== null)
{
diff --git a/Sources/Subs-Members.php b/Sources/Subs-Members.php
index 754e992..90dfe15 100644
--- a/Sources/Subs-Members.php
+++ b/Sources/Subs-Members.php
@@ -676,6 +676,9 @@ function registerMember(&$regOptions, $return_errors = false)
'openid_uri' => (!empty($regOptions['openid']) ? $regOptions['openid'] : ''),
);
+ require_once($sourcedir . '/scrypt.php');
+ $regOptions['register_vars']['passwd'] = Password::hash($regOptions['register_vars']['passwd']);
+
// Setup the activation status on this new account so it is correct - firstly is it an under age account?
if ($regOptions['require'] == 'coppa')
{
diff --git a/Sources/scrypt.php b/Sources/scrypt.php
new file mode 100644
index 0000000..1600635
--- /dev/null
+++ b/Sources/scrypt.php
@@ -0,0 +1,253 @@
+
+ * @license http://www.opensource.org/licenses/BSD-2-Clause BSD 2-Clause License
+ * @link http://github.com/DomBlack/php-scrypt
+ */
+
+/**
+ * This class abstracts away from scrypt module, allowing for easy use.
+ *
+ * You can create a new hash for a password by calling Password::hash($password)
+ *
+ * You can check a password by calling Password::check($password, $hash)
+ *
+ * @category Security
+ * @package Scrypt
+ * @author Dominic Black
+ * @license http://www.opensource.org/licenses/BSD-2-Clause BSD 2-Clause License
+ * @link http://github.com/DomBlack/php-scrypt
+ */
+abstract class Password
+{
+
+ /**
+ *
+ * @var int The key length
+ */
+ private static $_keyLength = 32;
+
+ /**
+ * Get the byte-length of the given string
+ *
+ * @param string $str Input string
+ *
+ * @return int
+ */
+ protected static function strlen( $str ) {
+ static $isShadowed = null;
+
+ if ($isShadowed === null) {
+ $isShadowed = extension_loaded('mbstring') &&
+ ini_get('mbstring.func_overload') & 2;
+ }
+
+ if ($isShadowed) {
+ return mb_strlen($str, '8bit');
+ } else {
+ return strlen($str);
+ }
+ }
+
+ /**
+ * Generates a random salt
+ *
+ * @param int $length The length of the salt
+ *
+ * @return string The salt
+ */
+ public static function generateSalt($length = 8)
+ {
+ $buffer = '';
+ $buffer_valid = false;
+ if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
+ $buffer = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
+ if ($buffer) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
+ $cryptoStrong = false;
+ $buffer = openssl_random_pseudo_bytes($length, $cryptoStrong);
+ if ($buffer && $cryptoStrong) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && is_readable('/dev/urandom')) {
+ $f = fopen('/dev/urandom', 'r');
+ $read = static::strlen($buffer);
+ while ($read < $length) {
+ $buffer .= fread($f, $length - $read);
+ $read = static::strlen($buffer);
+ }
+ fclose($f);
+ if ($read >= $length) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid || static::strlen($buffer) < $length) {
+ $bl = static::strlen($buffer);
+ for ($i = 0; $i < $length; $i++) {
+ if ($i < $bl) {
+ $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
+ } else {
+ $buffer .= chr(mt_rand(0, 255));
+ }
+ }
+ }
+ $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($buffer));
+
+ return $salt;
+ }
+
+ /**
+ * Create a password hash
+ *
+ * @param string $password The clear text password
+ * @param string $salt The salt to use, or null to generate a random one
+ * @param int $N The CPU difficultly (must be a power of 2, > 1)
+ * @param int $r The memory difficultly
+ * @param int $p The parallel difficultly
+ *
+ * @return string The hashed password
+ */
+ public static function hash($password, $salt = false, $N = 32768, $r = 8, $p = 1)
+ {
+ if ($N == 0 || ($N & ($N - 1)) != 0) {
+ throw new \InvalidArgumentException("N must be > 0 and a power of 2");
+ }
+
+ if ($N > PHP_INT_MAX / 128 / $r) {
+ throw new \InvalidArgumentException("Parameter N is too large");
+ }
+
+ if ($r > PHP_INT_MAX / 128 / $p) {
+ throw new \InvalidArgumentException("Parameter r is too large");
+ }
+
+ if ($salt === false) {
+ $salt = self::generateSalt();
+ } else {
+ // Remove dollar signs from the salt, as we use that as a separator.
+ $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($salt));
+ }
+
+ $hash = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
+
+ return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $hash;
+ }
+
+ /**
+ * Calculates a hash from a clear text password using the info from a previous hash
+ *
+ * DO NOT USE except for legacy applications, use check() instead, because this is
+ * vulnerable to timing attacks if you use == to compare strings
+ *
+ * @param string $password The clear text password
+ * @param string $hash The hashed password
+ *
+ * @return boolean If the clear text matches
+ */
+ public static function calculateHash($password, $hash)
+ {
+ // Is there actually a hash?
+ if (!$hash) {
+ return false;
+ }
+
+ list ($N, $r, $p, $salt, $hash) = explode('$', $hash);
+
+ // No empty fields?
+ if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) {
+ return false;
+ }
+
+ // Are numeric values numeric?
+ if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) {
+ return false;
+ }
+
+ $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
+
+ return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $calculated;
+ }
+
+ /**
+ * Check a clear text password against a hash
+ *
+ * @param string $password The clear text password
+ * @param string $hash The hashed password
+ *
+ * @return boolean If the clear text matches
+ */
+ public static function check($password, $hash)
+ {
+ // Is there actually a hash?
+ if (!$hash) {
+ return false;
+ }
+
+ list ($N, $r, $p, $salt, $hash) = explode('$', $hash);
+
+ // No empty fields?
+ if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) {
+ return false;
+ }
+
+ // Are numeric values numeric?
+ if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) {
+ return false;
+ }
+
+ $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
+
+ // Use compareStrings to avoid timeing attacks
+ return self::compareStrings($hash, $calculated);
+ }
+
+ /**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ *
+ * Compare two strings to avoid timing attacks
+ *
+ * C function memcmp() internally used by PHP, exits as soon as a difference
+ * is found in the two buffers. That makes possible of leaking
+ * timing information useful to an attacker attempting to iteratively guess
+ * the unknown string (e.g. password).
+ *
+ * @param string $expected
+ * @param string $actual
+ *
+ * @return boolean If the two strings match.
+ */
+ public static function compareStrings($expected, $actual)
+ {
+ $expected = (string) $expected;
+ $actual = (string) $actual;
+ $lenExpected = static::strlen($expected);
+ $lenActual = static::strlen($actual);
+ $len = min($lenExpected, $lenActual);
+
+ $result = 0;
+ for ($i = 0; $i < $len; $i ++) {
+ $result |= ord($expected[$i]) ^ ord($actual[$i]);
+ }
+ $result |= $lenExpected ^ $lenActual;
+
+ return ($result === 0);
+ }
+}
diff --git a/Themes/default/scripts/script.js b/Themes/default/scripts/script.js
index 49f9205..bcc1101 100644
--- a/Themes/default/scripts/script.js
+++ b/Themes/default/scripts/script.js
@@ -634,7 +634,7 @@ function hashLoginPassword(doForm, cur_session_id)
if (!('opera' in window))
doForm.passwrd.autocomplete = 'off';
- doForm.hash_passwrd.value = hex_sha1(hex_sha1(doForm.user.value.php_to8bit().php_strtolower() + doForm.passwrd.value.php_to8bit()) + cur_session_id);
+ doForm.hash_passwrd.value = hex_sha1(doForm.user.value.php_to8bit().php_strtolower() + doForm.passwrd.value.php_to8bit());
// It looks nicer to fill it with asterisks, but Firefox will try to save that.
if (is_ff != -1)
@@ -652,7 +652,8 @@ function hashAdminPassword(doForm, username, cur_session_id)
if (typeof(hex_sha1) == 'undefined')
return;
- doForm.admin_hash_pass.value = hex_sha1(hex_sha1(username.php_to8bit().php_strtolower() + doForm.admin_pass.value.php_to8bit()) + cur_session_id);
+ doForm.admin_hash_pass.value = hex_sha1(username.php_to8bit().php_strtolower() + doForm.admin_pass.value.php_to8bit());
+
doForm.admin_pass.value = doForm.admin_pass.value.replace(/./g, '*');
}