1

To securely hash passwords, algorithms such as PBKDF2 do many iterations of a common hash such as SHA1. Are there certain ways that these iterations need to be done to be safe?

In particular, from password-hash.js:

function generateHash(algorithm, salt, password, iterations) {
  iterations = iterations || 1;
  try {
    var hash = password;
    for(var i=0; i<iterations; ++i) {
      hash = crypto.createHmac(algorithm, salt).update(hash).digest('hex');
    }

    return algorithm + '$' + salt + '$' + iterations + '$' + hash;
  } catch (e) {
    throw new Error('Invalid message digest algorithm');
  }
}

Is this a safe way to hash the password? I can imagine that if you have an algorithm such as this that does:

HMAC(HMAC(HMAC(password)))

then there exists some faster function

HMAC3(password)

that gives the same output. Is this the case?

Sjoerd
  • 28,707
  • 12
  • 74
  • 102
  • Please read: [security..SE/How to securely hash passwords?](http://security.stackexchange.com/a/31846/2113) – Jacco May 19 '16 at 10:48
  • 1
    Possible duplicate of [How to securely hash passwords?](http://security.stackexchange.com/questions/211/how-to-securely-hash-passwords) – Jacco May 19 '16 at 10:48
  • Whenever possible, password hashing should be implemented in native code (compiled language), because the more rounds you can do in a given time, the better. – martinstoeckli May 19 '16 at 10:50
  • 1
    [Why does PBKDF2 xor the iterations of the hash function together?](http://crypto.stackexchange.com/questions/135/why-does-pbkdf2-xor-the-iterations-of-the-hash-function-together) – Sjoerd May 19 '16 at 11:34
  • [Why do I need to add the original salt to each hash iteration of a password?](http://crypto.stackexchange.com/questions/12795/why-do-i-need-to-add-the-original-salt-to-each-hash-iteration-of-a-password) – Sjoerd May 19 '16 at 14:21

1 Answers1

2

Yes, there are several requirements for an iterative hash function:

  • It should not be possible to do any precomputation, such as using rainbow tables.
  • The implementation should avoid running into cycles or fixed points in the hash function.
  • The implementation should be as fast as possible, because that is what the attacker would use.

Especially on the final point the example code is lacking. The crypto.createHmac(algorithm, salt) should really be outside of the loop. Because the attacker hashes many passwords with the same salt, he can even place the createHmac call outside of the password loop, e.g:

hmac = crypto.createHmac(algorithm, salt)
for (password in allpasswords) {
    var hash = password;
    for(var i=0; i<iterations; ++i) {
        hash = hmac.update(hash).digest('hex');
    }
    ...
}

This gives the attacker an advantage when cracking passwords, which would be avoided if the HMAC used the password instead of the salt as the key, as PBKDF2 does.

By implementing the given code in C and doing some optimizations, I managed to get it to be 10 times faster than the original NodeJS implementation. This means that an attacker can crack passwords 10 times faster than you intended, without even using a GPU or FPGA.

So there does exist a function that does a fast version of HMAC(HMAC(HMAC(password))), simply because HMAC does much the same work in every iteration.

I wrote a blog post about this.

Sjoerd
  • 28,707
  • 12
  • 74
  • 102