How not to generate passwords

A vendor of ours recently outgrew their “startup mentality,” and started using a support panel for their customer requests. As a consequence, all existing customers whoʼd just call in or send an e-mail before, were mass-enrolled in their new system.

This isnʼt an uncommon phenomenon on the internet: an account is created by the system owner, and you get a confirmation e-mail with a random password. No biggie.

This particular e-mail, however, first made me frown, then giggle like an idiot, then cry, then feeling sorry for humanity.

This is the gist of the e-mail we got:

Dear valued customer,

Thank you for choosing IniTech.
Here are your login details for our support panel:

User ID:   mainpress
Password:  $1$Wp8cWAg7$stCx1RGCLwwZmYJi9eKRI0

Sincerely,
IniTech Support

To me, this smells very fishy. If you havenʼt guessed already, the password that was generated for us is also a valid output of md5_crypt, a function we used millennia ago for securely storing passwords.

Since I donʼt have the opportunity to look behind the scenes, I can only guess why this particular password was generated, and all possible scenarios are bad.

How it should be done

As a quick recap, when generating new or one-time passwords one should use a cryptographically secure pseudorandom number generator (CSPRNG) that has been seeded with at least 256 bits of entropy.

When storing and checking submitted passwords (regardless of whether or not the password was set by a machine or by the user), use a memory-hard salted one-way function that has been designed for this purpose. Examples include bcrypt, but note that the best practices for doing this are a moving target, so donʼt take last weekʼs advice at face value.

Also, only use cryptographic primitives that havenʼt been broken yet. Using MD5 in {current_year} is really not an option anymore.

Benefit of the doubt

It should be said: they could have used a CSPRNG correctly and still have arrived at the password we see above.

Letʼs estimate the probability for that, shall we?

A valid output of md5_crypt consists of a fixed header $1$, 8 base64 characters for the salt, another $, and ends with 22 base64 characters for the hash value. Letʼs assume they allow any printable ASCII character (except space) in passwords (94 characters). Heck, letʼs assume some punctuation looked at the developer funny, and their password alphabet has 80 characters.

The probability that the generated password starts with $1$ and has another $ on position 11 is (1/80)4, or about 0.0000024%. The probability that the 30 other characters in this password donʼt venture outside the base64 range is (64/80)30, or about 1.24%.

Multiplying those, we get a solid “that probably wasnʼt the case.”

Sending the wrong value

It could also be possible that they generate a random password, hash it for storage, and accidentally send the hash value instead of the actual password.

Apart from the fact that they really shouldnʼt be using MD5 for anything, this by itself isnʼt quite as bad as some of the other scenarios below. However, it would mean that the e-mail we got doesnʼt contain our actual password, but rather the hashed value, which would mean we canʼt log in using this value; weʼd have to crack it first.

This scenario was easily falsified: we could log in using this value.

Still in the process of encrypting passwords

A variant of the above scenario, Itʼs also entirely possible that theyʼre currently in the process of changing their password storage strategy (and have been for 15 years, judging by their choice of hashes) and not all processes have got the memo yet.

The 'passwordʼ field in their user database contains a mix of hashed and plaintext passwords, and to make sure their login routine works in both cases, theyʼd do something like this:

SELECT id FROM users
WHERE username = '" . $_POST["username"] . "'
    AND ( md5_crypt_verify( Password, '" . $_POST["password"] . "' ) = 1
        OR  Password = '" . $_POST["password"] . "' )

(SQL injection added for dramatic effect.)

Short of cracking the hash, thereʼs no real way of verifying this scenario though.

“Adding randomness”

The most likely culprit: theyʼre using MD5 to make random passwords of low quality appear “more random.”

For the uninitiated, this is not possible; applying a deterministic operation on your value will not add any randomness that wasnʼt already there, itʼll just transform it. So if the input value could be guessed easily, the final value will too.

Over the years Iʼve seen lots of examples on StackOverflow of people using bad randomness, and then misusing hash functions to somehow launder it into proper randomness. Stuff like:

function random_password()
{
    return md5(time());
}

or

function super_random_password()
{
    mt_srand(time());
    $c = "";
    for ( $i = 0; $i < 6; $i++ )
        $c .= chr(mt_rand(0,255));
    return md5($c);
}

If they seeded their RNG with the current time like the example above, we can quickly guess the entire random sequence, since we know the approximate time the e-mail was sent. The few variants of the above code I tried all came up negative, so unless I stumble upon another one (or get a look at the code) this will forever be a mystery.

As an aside, in this scenario the benefit of using md5_crypt over just md5 is that md5_crypt requires a random salt value, so if that salt was properly generated, the password may still be of okayish quality.

Diagnosis: fishy.

The three possible scenarios outlined above are by no means the only possibilities for whatʼs going on here. However, the trend is clear: in the worst case theyʼre revealing more information than they should, in the best case theyʼre using something for a purpose for which it wasnʼt designed.

Either way, the function md5_crypt is insecure and should not be used for anything. The fact that they e-mailed its raw output to me is indicative of bad security practice and therefore highly worrying.

¹: https://gist.github.com/thijzert/f252ba3ec4416c64f45117e46d05aa55