php - "Keep Me Logged In"- 最好的方法

标签 php security session remember-me

我的网络应用程序使用 session 来存储用户登录后的信息,并在用户在应用程序内从一个页面到另一个页面时维护这些信息。在这个特定的应用程序中,我存储了此人的 user_idfirst_namelast_name

我想在登录时提供一个“让我保持登录”选项,它将在用户的计算机上放置一个 cookie 两周,当他们返回应用程序时,它将以相同的详细信息重新启动他们的 session 。

执行此操作的最佳方法是什么?我不想将他们的 user_id 存储在 cookie 中,因为这似乎会让一个用户很容易尝试伪造另一个用户的身份。

最佳答案

好的,让我直截了本地说:如果您为此目的将用户数据或从用户数据派生的任何内容放入 cookie,那么您做错了什么。

那里。我说了。现在我们可以继续讨论实际的答案了。

您问,散列用户数据有什么问题?嗯,它归结为暴露表面和通过默默无闻的安全性。

想象一下你是一个攻击者。您会在 session 中看到为 remember-me 设置的加密 cookie。它有 32 个字符宽。哎呀。那可能是 MD5...

让我们也想象一下,他们知道您使用的算法。例如:

md5(salt+username+ip+salt)

现在,攻击者需要做的就是暴力破解“盐”(这实际上不是盐,但稍后会详细介绍),他现在可以使用任何用户名生成他想要的所有假 token IP地址!但是暴力破解盐很难,对吧?绝对地。但现代 GPU 非常擅长它。除非您在其中使用足够的随机性(使其足够大),否则它会迅速折叠,并随之掉落您城堡的 key 。

简而言之,保护你的唯一东西就是盐,它并没有你想象的那样真正保护你。

等一下!

所有这些都假设攻击者知道算法!如果它是 secret 和令人困惑的,那么你是安全的,对吧? 错误。这种思路有一个名字:Security Through Obscurity,应该绝不依赖它。

更好的方法

更好的方法是永远不要让用户的信息离开服务器,除了 id。

当用户登录时,生成一个大的(128 到 256 位)随机 token 。将其添加到将 token 映射到用户 ID 的数据库表中,然后将其发送到 cookie 中的客户端。

如果攻击者猜到另一个用户的随机 token 怎么办?

好吧,让我们在这里做一些数学运算。我们正在生成一个 128 位随机 token 。这意味着有:

possibilities = 2^128
possibilities = 3.4 * 10^38

现在,为了说明这个数字是多么的大,让我们想象一下互联网上的每台服务器(比如说今天的 50,000,000 台)都试图以每台每秒 1,000,000,000 的速度暴力破解这个数字。实际上,您的服务器会在这样的负载下崩溃,但让我们来解决这个问题。

guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000

所以每秒有 50 万亿次猜测。真快!对吧?

time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000
time_to_guess = 6,800,000,000,000,000,000,000

所以 6.8 六亿秒...

让我们试着把它归结为更友好的数字。

215,626,585,489,599 years

甚至更好:

47917 times the age of the universe

是的,那是宇宙年龄的 47917 倍……

基本上不会被破解。

总结一下:

我推荐的更好的方法是将 cookie 存储为三个部分。

function onLogin($user) {
    $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
    storeTokenForUser($user, $token);
    $cookie = $user . ':' . $token;
    $mac = hash_hmac('sha256', $cookie, SECRET_KEY);
    $cookie .= ':' . $mac;
    setcookie('rememberme', $cookie);
}

然后,验证:

function rememberMe() {
    $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
    if ($cookie) {
        list ($user, $token, $mac) = explode(':', $cookie);
        if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
            return false;
        }
        $usertoken = fetchTokenByUserName($user);
        if (hash_equals($usertoken, $token)) {
            logUserIn($user);
        }
    }
}

注意:不要使用 token 或用户和 token 的组合来查找数据库中的记录。始终确保根据用户获取记录,然后使用时间安全比较功能来比较获取的 token 。 More about timing attacks .

现在,SECRET_KEY 是一个密码 secret (由 /dev/urandom 之类的东西生成和/或派生自高熵输入)。此外,GenerateRandomToken() 需要是强随机源(mt_rand() 还不够强。使用库,例如​​ RandomLibrandom_compat ,或 mcrypt_create_iv()DEV_URANDOM)...

hash_equals()是为了防止timing attacks . 如果您使用低于 PHP 5.6 的 PHP 版本,函数 hash_equals()不支持。在这种情况下,您可以替换 hash_equals()使用timingSafeCompare函数:

/**
 * A timing safe equals comparison
 *
 * To prevent leaking length information, it is important
 * that user input is always used as the second parameter.
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */
function timingSafeCompare($safe, $user) {
    if (function_exists('hash_equals')) {
        return hash_equals($safe, $user); // PHP 5.6
    }
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    // mbstring.func_overload can make strlen() return invalid numbers
    // when operating on raw binary strings; force an 8bit charset here:
    if (function_exists('mb_strlen')) {
        $safeLen = mb_strlen($safe, '8bit');
        $userLen = mb_strlen($user, '8bit');
    } else {
        $safeLen = strlen($safe);
        $userLen = strlen($user);
    }

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;
}

关于php - "Keep Me Logged In"- 最好的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1354999/

相关文章:

session - 可扩展 Web 应用程序的无状态登录/身份验证机制?

php - 以编程方式自动填写和提交表单

php - Laravel 属于抛出 undefined 错误

java - Android 应用程序进行 JSON 调用从服务器获取意外数据,但相同的调用在浏览器中有效

security - 如果我检测到我的网站受到攻击,我应该记录哪些信息?

sql-server - Sql Server 2005如何更改dbo登录名

php - 数据库和用户照片管理

Java:安全异常 - 非法 url 重定向

php - 登录后显示用户名

javascript - 为什么对对象的引用被其值替换