Security Answer of the Week, Websec Advice Edition — Part II

Feb 06, 2010

(Continued from part 1…)

A beginning: Passwords

Our journey begins not, as one might surmise, with slide 0 or slide 1. Not, in other words, at the beginning — for the beginning is uncontroversial. No, dear reader, we shall not be wasting any more time than has already been lost. Instead, we begin with slide 14 — a slide with the unassuming title:

Plaintext Passwords IV Fix

We meet up with our intrepid explorers as they are recovering from “Plaintext Passwords IV” — which is to say, as they learn that One Should Hash Passwords With A Random Salt. As we said, uncontroversial — this is indeed good advice, consistent with all that is good and right with the world.

But our protagonists were not satisfied. No, when they gazed at The Solution, they did not see the harmony and beauty of this answer. Instead, they saw only darkness and destruction — and it is into that vision that we too must now peer:

But lo!, the good Mr. Brad Greenlee cried out, this is

only using 4 chars of the salt for no clear reason, and with the salt limited to hex digits, the salt-space is even smaller (65536 possible values)

and

I believe PHP’s rand() isn’t a very good PRNG. mt_rand() is better…although it would be better to have a salt space that is greater than just getrandmax()/mt_getrandmax() values.

All reasonable points. It’s unclear why the author has chosen to restrict the salt to 16 bits: ceteris paribus, the larger the salt keyspace, the better.1 And PHP’s random-number generation code is, frankly, utterly broken.2 On the other hand, if what we’re worrried about is precomputation (“rainbow table”) attacks, even these few bits of entropy will still increase the amount of work necessary by a factor of 2^16. Not enough to stop a determined attacker, perhaps, but probably enough of a hassle to dissuade more casual bad guys. For this observation, the intrepid Mr. Greenlee found himself 2 points closer to enlightenment.

But Brad was not sated. He peered still closer at the Proposed Solution, and was horrified to find a still deeper problem, just below the surface, peering back.

The password hash could be improved by using something more compute-intensive, like bcrypt

he cried; and this time, his voice was joined by a fellow traveler, a certain Coda Hale, who cried out

MD5 instead of bcrypt

somewhat more cryptically.

Truly it was this flaw, much more serious than the last, that drew our heroes’ attention to the cracks in the façade of Web Security Advice they had been faced with. For this was no minor quibble about how threat models and the determination of our attackers. This was a serious threat to the author’s vision of a peaceful and harmonious world. Computing an MD5 hash on ordinary hardware, it turns out, is fast — really, really fast. Hundreds of millions of hashes per second fast. At these speeds, rainbow tables are no longer interesting: an attacker can easily brute-force just about any password in a trivial amount of time, salt or no salt.

The correct answer here is simple: use bcrypt, scrypt, or PBKDF2 — something that has been designed to be slow and hard to compute. A simple MD5 hash does not come anywhere near sufficiency these days. This realization, no matter how depressing, nevertheless earned our two protagonists 10 further points each.

Continued in part three, to be posted shortly…


  1. Up to the hash size, of course.

  2. By default, the only source of entropy PHP has is the process ID and a couple of timestamps. This weakness, and the fact that neither rand() nor mt_rand() is anything like a cryptographically-secure RNG, has already been responsible for a very impressive break of PHP session IDs. The PHP project’s supposed fix for the problem does essentially nothing, adding at most a bit or two of entropy to the mix; most troubling, however, is the primary author’s rather stubborn insistence that everything is OK when these issues were reported. If you are forced to use PHP, for whatever reason, I recommend setting session.entropy_file="/dev/urandom" and session.entropy_length="128" in php.ini, as well as reading directly from /dev/urandom rather than calling any of the rand() or mt_rand() functions.