3

Assumed I have to following code in order to generate a random blowfish salt for saving a new password:

$blowfishCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./';
$salt = '';
$type = '2a';
$cost = '08';

$maxIndex = strlen($blowfishCharacters) - 1;

for ($i = 0; $i < 22; $i++) {
        $salt .= $blowfishCharacters[mt_rand(0, $maxIndex)];
}

$salt = '$' . $type . '$' . $cost . '$' . $salt;
$pass = crypt($password, $salt);

I read several discussions about this topic, but they all came to different results. mt_rand always seems to be better, but is it suitable for this purpose?

2 Answers 2

5

Your salt doesn't need to be perfectly random, it just needs to be unique for each of your users. See this question on Crypto.SE for more. That's why some websites use the username to derive the salt from (ie $salt=$username.$signup_date).

In your case, when crypt says 'blowfish hash' it really means bcrypt so you also want to keep up with Moore's law when selecting the cost factor.


There are a few observations I made on your code though. First, strlen() does not include the NULL character at the end so strlen($blowfishCharacters) - 1; makes sure you'll never see a / in your salt. I'd also suggest using range() when building the array, but that's my personal preference. You can also completely do away with the loop since we don't like re-inventing the wheel (right?). Therefore your code now looks like this:

$salt=array_merge(range('a', 'z'), range('A', 'Z'), ['.', '/']); //generate the dictionary
shuffle($salt);
$salt=array_slice($salt, 0, 21); //cut to size

This will return a $salt array with 21 elements. The 22nd element required by crypt is actually the delimiter $.


To answer your original question: According to mt_rand()'s man page

By default, PHP uses the libc random number generator with the rand() function. The mt_rand() function is a drop-in replacement for this. It uses a random number generator with known characteristics using the » Mersenne Twister, which will produce random numbers four times faster than what the average libc rand() provides.

so it's not more "secure" it just runs faster.


In conclusion, you don't need a crypto-grade PRF to derive the salt from. That's because a salt guards against pre-computed hash tables. If you don't have a salt, the adversary can pre-generate a table and compare it against your entire database to find a match. The salt makes sure the adversary has to do that process for each entry in the database, thus making the attack extremely expensive with a high enough cost factor.

Of course setting the cost too high will make your server struggle when lots of users try to log in at the same time but I'd trade some risk of inconvenience for security any time.

So the answer to your question is yes mt_rand() is good enough.

Edit: This article is a good guide on bcrypt hygiene. Quoting section #2,

A perfect source would be /dev/urandom. Other sources would be mcrypt_create_iv when paired with MCRYPT_DEV_URANDOM. If neither of them are available, you can fall back to openssl_random_pseudo_bytes or mt_rand (at an absolute last resort).

so use mcrypt_create_iv instead.

1
  • 1
    Marked this as accepted for the research effort. In addition to tylerl's answer, mt_rand seems to be okay, however, I implemented random number generation via dev/urandom (see this link). Jul 30, 2013 at 17:42
7

Under most conditions, a salt needs only to be unique, not cryptographically unpredictable.

So in that case, yes as long as your RNG is properly seeded. Another common technique to improve salt quality is to concatenate a random number to the current time, then hash that (e.g. SHA1), textually encode (e.g. base64), and take the first N characters. The chances of a collision there are pretty slim.

Edit to explain
Here's an explanation of what's in this techique and why:

  • The current time prevents repitition. As long as two salts aren't generated at the same time, they won't be the same
  • The random number prevents predictability. You can't know the salt ahead of time because there's an element of randomness. This obviously assumes that the random number isn't readily predictable and isn't based on the current time. More importantly, it also helps prevent collisions if two salts happen to be generated at the same time.
  • The hash smooths out the distribution so each bit has an equal chance of being set. It also prevents you from knowing what went in to generating the salt just by looking at the result.
  • You can also concatenate a secret string to the input of your hash if you're paranoid about people predicting salt values. You shouldn't have a reason to care, though.
8
  • SHA1 is bad news for password hashing, even salted. A birthday attack will produce a collision in 2^80 iterations, for bcrypt this value is raised to 2^92, and that's not even considering the fact PHP's SHA1 is designed to be fast.
    – rath
    Jul 30, 2013 at 8:06
  • 4
    @rath that's not entirely true (as long as you use enough rounds and a salt) it's still secure enough for password hashing and not considered broken. However what tylerl says is that you can use the hashing function to derive a salt, this is perfectly suitable and secure and has no regards to what you have posted. Refer to the bear: stackoverflow.com/questions/2772014/… . Personally I also prefer bcrypt or scrypt. Jul 30, 2013 at 9:12
  • 3
    PBKDF2 is essentially built on top of sha1 (but you can also use sha256) and hashes the password 1.000 times (rounds) rather than one time (to make it slower, even if it's fast) Jul 30, 2013 at 10:41
  • 4
    @rath this particular answer suggests using SHA1 as a normalizing function, to flatten out the output and give you a good distribution across all possible salts. It has nothing to do hashing passwords.
    – tylerl
    Jul 30, 2013 at 16:42
  • 1
    THIS IS NOT ABOUT HASHING PASSWORDS. When you see a hash function used, do not assume it's about hashing passwords. Multiple iterations of a hash function is not appropriate in the context of this question or this answer.
    – tylerl
    Feb 26, 2014 at 17:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .