Xing \ Creative \ Coding

Web Application Software Development

In a another post, Why You MUST Hash Your User’s Passwords, I talked about the importance of hashing rather than encrypting (or worse storing in plain text) your user’s passwords. I also discussed why it is important to use a hashing algorithm designed to be “slow” such as bcrypt, scrypt, PBKDF2, and SHA512crypt. However, even if you use the slowest possible hashing algorithm ever known to man, if you don’t, also, salt the passwords, they will be much more easily compromised.

Unfortunately, your users are DUMB. They are going to use passwords like “molly77″. As a quick example, a SHA256 hash is considered fairly secure. To brute-force a SHA256 password would take hours even for the best machines. However, a SHA256 hash of a DUMB password is NOT secure at all. E.g. here’s the SHA256 hash of “molly77″:

1a17ea140e6d274c92bd053559f649370e35866c88a3da5e8065178810a82a03

Take that hash, and go to this link: https://crackstation.net/, paste it into the textbox, submit it, and you’ll find that it very quickly finds the result from a Rainbow Table lookup. A Rainbow Table is a list of hashes and their corresponding passwords, and a rainbow table can be easily generated when either you don’t salt your password or you use the same salt for every password. Thus, it is important that you create a random salt for each password whenever the user registers or updates their password. Salting is extremely easy, all you do is prepend or append the password with a string, e.g. our molly77 password becomes something like:

molly77257401b6e671b38ec98fc3fb107530f8

When we hash this new password string, it becomes:

d2131964d0071fa9d191199ad218632866fbf4744511d1eb29ac4f6247e7497e

And when we lookup this hash in the link I provided above, the rainbow table doesn’t work. It’s so easy that there is no valid reason not to do it. The only additional work is storing the salt in the user row alongside the hashed password so that when the user logs in with the right password, we can produce the same hash. The method for doing this is something like this:

isValidPassword( submittedPassword ) {
    // areHashesEqual needs to be a "length constant" comparison method
    // as using "==" is vulnerable to timing attacks
    return areHashesEqual(hashMethod(submittedPassword + salt), hashedPassword);
}

Randomly salting your passwords makes rainbow tables useless and forces a would-be attacker to use brute-force methods if they want to crack your users’ passwords. If you’ve, additionally, used a slow hash like PBKDF2, bcrypt, etc., then brute-force methods become much more expensive, and it becomes much less likely that an attacker will bother with the passwords even if they happen to get a dump of your database table.

Best

  • Use a well-supported PBKDF2, BCrypt, or SCrypt library that takes care of randomly generating the salt for you and allows you to increase the rounds progressively.
  • Use said library’s “length-constant” method to compare the stored hash with the password submitted by the user.

Second Best

  • Use a “slow”hashing algorithm
  • Generate a sufficiently long salt. A rainbow table can still be generated if your salt has too few possible combinations. The PBKDF2 recommendation is a minimum of 8 bytes.
  • At the bottom of this page, I’ve posted a PHP example for using the crypt method correctly before PHP 5.5 when password_hash() and hash_equals() become available.

Don’t

  • Try to improve the hashing by coming up with some super-secret way to salt your passwords. It’s a waste of time. You should assume that if the user gets a data dump, they also have your source code, in which case you’ve done nothing except add more work for no additional security.
<?php
namespace Modules\Authentication\Helpers {
    class BCrypt {
        public static function hash( $password, $cost=12 ) {
            $base64 = base64_encode(openssl_random_pseudo_bytes(17));
            $salt = str_replace('+','.',substr($base64,0,22));
            $cost = str_pad($cost,2,'0',STR_PAD_LEFT);
            $algo = version_compare(phpversion(),'5.3.7') >= 0 ? '2y' : '2a';
            $prefix = "\${$algo}\${$cost}\${$salt}";

            return crypt($password, $prefix);
        }
        public static function isValidPassword( $password, $storedHash ) {
            $newHash = crypt( $password, $storedHash );
            return self::areHashesEqual($newHash,$storedHash);
        }
        private static function areHashesEqual( $hash1, $hash2 ) {
            $length1 = strlen($hash1);
            $length2 = strlen($hash2);
            $diff = $length1 ^ $length2;
            for($i = 0; $i < $length1 && $i < $length2; $i++) {
                $diff |= ord($hash1[$i]) ^ ord($hash2[$i]);
            }
            return $diff === 0;
        }
    }
}

February 5th, 2015

Posted In: Security

Tags: , ,

One Comment

Leave a Reply