From fb9cf72df9b68ed6461d979772aac7b1eab697f1 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Thu, 28 Apr 2016 20:58:40 -0400 Subject: [PATCH] Switch passwords and secret answers to scrypt --- Sources/LogInOut.php | 121 ++------------- Sources/Profile-Modify.php | 32 +++- Sources/Profile.php | 3 +- Sources/Register.php | 16 +- Sources/Reminder.php | 9 +- Sources/Subs-Auth.php | 3 + Sources/Subs-Members.php | 3 + Sources/scrypt.php | 253 +++++++++++++++++++++++++++++++ Themes/default/scripts/script.js | 5 +- 9 files changed, 319 insertions(+), 126 deletions(-) create mode 100644 Sources/scrypt.php 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, '*'); }