“让我保持登录"- 最好的方法 [英] "Keep Me Logged In" - the best approach

查看:19
本文介绍了“让我保持登录"- 最好的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的网络应用程序使用会话来存储用户登录后的信息,并在他们在应用程序内从页面移动到页面时维护这些信息.在这个特定的应用程序中,我存储了此人的 user_idfirst_namelast_name.

My web application uses sessions to store information about the user once they've logged in, and to maintain that information as they travel from page to page within the app. In this specific application, I'm storing the user_id, first_name and last_name of the person.

我想在登录时提供一个让我保持登录"选项,它将在用户的机器上放置一个 cookie 两周,当他们返回应用程序时,这将使用相同的详细信息重新启动他们的会话.

I'd like to offer a "Keep Me Logged In" option on log in that will put a cookie on the user's machine for two weeks, that will restart their session with the same details when they return to the app.

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

What is the best approach for doing this? I don't want to store their user_id in the cookie, as it seems like that would make it easy for one user to try and forge the identity of another user.

推荐答案

好吧,让我直言不讳:如果您为此目的将用户数据或从用户数据派生的任何内容放入 cookie,您正在做有事吗.

OK, let me put this bluntly: if you're putting user data, or anything derived from user data into a cookie for this purpose, you're doing something wrong.

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

There. I said it. Now we can move on to the actual answer.

你问散列用户数据有什么问题?好吧,它归结为通过默默无闻来暴露表面和安全性.

What's wrong with hashing user data, you ask? Well, it comes down to exposure surface and security through obscurity.

想象一下你是一名攻击者.您会在会话中看到为记住我"设置的加密 cookie.它是 32 个字符宽.啧.那可能是一个 MD5...

Imagine for a second that you're an attacker. You see a cryptographic cookie set for the remember-me on your session. It's 32 characters wide. Gee. That may be an MD5...

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

Let's also imagine for a second that they know the algorithm that you used. For example:

md5(salt+username+ip+salt)

现在,攻击者需要做的就是暴力破解salt"(这不是真正的盐,但稍后会详细介绍),并且他现在可以使用任何用户名生成他想要的所有假令牌IP地址!但是强制加盐很难,对吧?绝对地.但是现代 GPU 在这方面非常擅长.除非你在其中使用足够的随机性(使其足够大),否则它会迅速倒塌,并随之成为你城堡的钥匙.

Now, all an attacker needs to do is brute force the "salt" (which isn't really a salt, but more on that later), and he can now generate all the fake tokens he wants with any username for his IP address! But brute-forcing a salt is hard, right? Absolutely. But modern day GPUs are exceedingly good at it. And unless you use sufficient randomness in it (make it large enough), it's going to fall quickly, and with it the keys to your castle.

简而言之,唯一能保护你的是盐,它并没有像你想象的那样真正保护你.

In short, the only thing protecting you is the salt, which isn't really protecting you as much as you think.

等等!

所有这些都假设攻击者知道算法!如果它是秘密和令人困惑的,那么你是安全的,对吧?错误.这种思路有一个名字:通过默默无闻的安全,应该永远依赖.

All of that was predicated that the attacker knows the algorithm! If it's secret and confusing, then you're safe, right? WRONG. That line of thinking has a name: Security Through Obscurity, which should NEVER be relied upon.

更好的方法

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

The better way is to never let a user's information leave the server, except for the id.

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

When the user logs in, generate a large (128 to 256 bit) random token. Add that to a database table which maps the token to the userid, and then send it to the client in the cookie.

如果攻击者猜测另一个用户的随机令牌怎么办?

What if the attacker guesses the random token of another user?

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

Well, let's do some math here. We're generating a 128 bit random token. That means that there are:

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

现在,为了说明这个数字有多大,让我们想象一下互联网上的每台服务器(假设今天有 50,000,000 台)试图以每台每秒 1,000,000,000 台的速度对这个数字进行暴力破解.实际上,您的服务器会在这种负载下崩溃,但让我们来看看吧.

Now, to show how absurdly large that number is, let's imagine every server on the internet (let's say 50,000,000 today) trying to brute-force that number at a rate of 1,000,000,000 per second each. In reality your servers would melt under such load, but let's play this out.

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 千万次猜测.真快!对吗?

So 50 quadrillion guesses per second. That's fast! Right?

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 六亿秒...

So 6.8 sextillion seconds...

让我们尝试将其简化为更友好的数字.

Let's try to bring that down to more friendly numbers.

215,626,585,489,599 years

甚至更好:

47917 times the age of the universe

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

Yes, that's 47917 times the age of the universe...

基本上,它不会被破解.

Basically, it's not going to be cracked.

总结一下:

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

The better approach that I recommend is to store the cookie with three parts.

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);
        }
    }
}

注意:不要使用令牌或用户和令牌的组合来查找数据库中的记录.始终确保根据用户获取记录,然后使用定时安全比较函数来比较获取的令牌.更多关于定时攻击.

Note: Do not use the token or combination of user and token to lookup a record in your database. Always be sure to fetch a record based on the user and use a timing-safe comparison function to compare the fetched token afterwards. More about timing attacks.

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

Now, it's very important that the SECRET_KEY be a cryptographic secret (generated by something like /dev/urandom and/or derived from a high-entropy input). Also, GenerateRandomToken() needs to be a strong random source (mt_rand() is not nearly strong enough. Use a library, such as RandomLib or random_compat, or mcrypt_create_iv() with DEV_URANDOM)...

hash_equals() 是为了防止 定时攻击.如果您使用 PHP 5.6 以下的 PHP 版本,则函数 hash_equals() 不是支持的.在这种情况下,您可以将 hash_equals() 替换为timingSafeCompare 函数:>

The hash_equals() is to prevent timing attacks. If you use a PHP version below PHP 5.6 the function hash_equals() is not supported. In this case you can replace hash_equals() with the timingSafeCompare function:

/**
 * 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;
}

这篇关于“让我保持登录"- 最好的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆