hash - Is Blowfish necessary for security token in PHP remember me system?

395

I've search SO and I know there are a lot of “Remember Me” questions but I couldn't find one specifically about using Blowfish for generating security tokens.

I'm implementing a login system to use cookie-based “Remember Me” functionality and all the tutorials I've read either don't look secure (e.g. just an unsalted md5 hash based on the time) or look overly complicated.

I am not building sites for a bank or anything like that but just want a reasonable level of security.

The system I am proposing is to simply create a 128 char random string for the user name and a second 128 char string for a login token. The raw strings would be stored in a cookie and unsalted sha1'd versions would go in the row of their user account in the database.

I guess I could even regenerate the strings on every page load.

To me this offers decent security (I think!) because:

  1. A hacker can't target a specific account but knowing their user name (they would need to know the 128 char string)
  2. To forge the login cookie and gain access to the account someone would have to guess 2 x 128 char strings.
  3. If the database is hacked creating a rainbow table for 128 char strings is too difficult.
  4. I can safely remember the user name only with storing the actual user name in the cookie.

My questions are:

  1. Does this sounds reasonably secure? Should I be using strings longer than 128 chars?
  2. Is it worth using Blowfish instead of sha1? (I.e. am I right in saying the rainbow table is too difficult to produce?)
  3. If not, is there any advantage to running sha1 say 1,000 times?

Many thanks.

65

Answer

Solution:

Well, there are two things that blowfish can mean. The Cipher (two way encryption algorithm) or the Hash (one way hashing algorithm, specifically called bcrypt). But more on that in a bit.

So let's look at your assumed advantages.

  1. A hacker can't target a specific account by knowing their user name.

    Even if they knew the username, it wouldn't matter. The 128 bit random string is large enough that you really don't need to worry about an attacker guessing it. Let's do some math.

    2^128 == 3 * 10^38 possible combinations
    
    Assuming there are 50 million servers on the internet,
    And each server can do 100,000,000,000 guesses per second (to your server)
    
    3e38 / 50,000,000 / 100,000,000,000 == 6 * 10^19 seconds
    
    Which when converted to years is: 1,902,587,519,025
    
    To have a 50% chance of guessing it, the attacker would need to hit 1/2:
    
    Years to 50% chance of guessing: 951,293,759,512
    

    So unless you are concerned about an attacker trying to break into your system in the next trillion years, 128 bit is plenty strong enough...

  2. To forge the login cookie ... would have to guess 2 x 128 char strings

    We've already shown that guessing 1 is not going to happen. So adding a second is not necessary (it may not be bad, but it's not needed)

  3. If the database is hacked creating a rainbow table for 128 char strings is too difficult

    Yes. However that's not what you should be concerned about. What you should be concerned about is brute forcing. But more on that later...

So, to answer your actual questions:

  1. Does this sound reasonably secure.

    Reasonably, sure. Over-complicated: definitely. There are simpler ways of dealing with this...

  2. Is it worth using Blowfish instead of sha1.

    No. Blowfish is for derivations (meaning where you want a proof of work). In this case you want to generate a MAC (machine authentication code). So I wouldn't use either Blowfish or SHA1. I would use SHA256 or SHA512...

  3. Is there an advantage to running sha1 1000 times.

    No

The better way

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

Then, to validate:

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

Now, it's very important that theSECRET_KEY be a cryptographic secret (generated by something like/dev/random 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 or mcrypt with DEV_URANDOM)...

And timingSafeCompare is to prevent timing attacks. Something like this:

/**
 * 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) {
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    $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;
}
440

Answer

Solution:

As far as i can judge, the cruial point is, how you generate the random token, and what alphabet you allow in this string. Assuming that you use the random source of the operating system to generate this 128 character string, and allow case sensitive characters and digits, you get a very strong and unpredictable "password".

While my feeling tells me to use BCrypt (there is no reason against it), pure logic says that:

  1. 128 characters are much longer than usual salted passwords, so salting is not necessary.
  2. It is impossible to brute-force 62^128 = 2E229 combinations. Dictionary attacks are out of question too because of the random nature of the token. That means iterating the hash-function is not necessary.

I would not use SHA-1 though, this reduces the entropy of your safe token to 160 bit. It doesn't make sense to create a super strong token, only to store part of it, instead use SHA-256 or SHA-512.

There are other things to consider, configure the cookie, so it can only be sent over HTTPS. Actually you will face the same problems as with maintaining a session.

298

Answer

Solution:

If you are using SSL to create a secure connection to exchange the tokens then I would say that this is fairly secure. I would just use something like a guid though for the token, not blowfish or SHA1. Those really don't get you anything, when what you really want is a large random string of bits.

If you aren't creating a secure connection in the first place then the man in the middle attack is likely and very easy to accomplish. Since SSL creates a random key each time, there really is no reason to change the token with each page load.

People are also looking for solutions to the problem: php - mod_rewrite is behaving strangely - some RewriteRules work and others don't

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.