Why is it always `HASH( salt + password )` that we recommend?

Actually, "we" are not recommending any of what you show. The usual recommendations are PBKDF2, bcrypt or the SHA-2 based Unix crypt currently used in Linux.

If the hash function you use is a perfect random oracle then it does not really matter which way you input the salt and the password; only matters the time it takes to process the salt and password, and we want that time to be long, so as to deter dictionary searches; hence the use of multiple iterations. However, being a perfect random oracle is a difficult property for a hash function; it is not implied by the usual security properties that secure hash functions must provide (resistance to collisions and to preimages) and it is known that some widely used hash functions are not random oracles; e.g. the SHA-2 functions suffer from the so-called "length extension attack", which does not make them less secure, but implies some care when using the function in funky password-hashing schemes. PBKDF2 is often used with HMAC for that reason.

You are warmly encouraged not to feel creative with password hashing schemes or cryptography in general. Security relies on details which are subtle and which you cannot test by yourself (during tests, an insecure function works just as well than a secure one).


On the two proposed -

function (salt, pass) {    return
    (StrongHash( StrongHash(salt) + StrongHash(pass) ) 
}

There's no bonus here. A hash renders information into something random. So on the first pass, you made two pieces of data into two random strings and then combined two random strings. Why? Security through obscurity offers no benefit. The critical elements of the generally proposed solution are:

  • combine salt & password so that the salt can provide an added chaos factor for creating a password's hash
  • one way hash the conglomeration to make a seemingly random string.

You can't be more random than random - and with security it's good to avoid work that doesn't have a purpose.

function (salt, pass) {
   var data = salt + pass;
   for (i=0;i < 1000; i++ {
       data += StrongHash(salt + data)
   }
   return (data)
}

The first thing happening here is that the data is given the salt and the password. So your output will look like:

<Salt><Password><randomString0><randomString1>....<randomString999>

<Salt> = the salt in cleartext
<Password> = the password in cleartext
<randomString> = a series of different random strings.

The code as written just exposed the password, so all the random strings serve no purpose.

A fix to this problem would be:

function (salt, pass) {
   var data = salt + pass;
   for (i=0;i < 1000; i++ {
       data = StrongHash(salt + data)
   }
   return (data)
}

Note removal of the += and change to a simple assignment. It's a small change, but it means that now your final data is a single random string of the length that your hash algorithm outputs.

That's probably fine from a security perspective, but there's, again, no real improvement over the simple original version. The repetition of many recursive hashes adds no security - your first pass with a hash algorithm should produce a random result. Hashing the same thing over and over again does nothing in the best case, and worst case could end up reducing the value of the hash algorithm.

The first way offers a very significant benefit - the KISS principal. Keep It Simple. When repeating the function offers no benefit, there's no reason to make your logic more complicated, longer to process, more open to error and harder to debug.

Also - with cryptography, all algorithms should come with a user manual that would fill your average cubicle. The discussion of weak keys, problems with repetition, exposure and other mathematical input/output debates will keep mathematicians stocked with thesis topics for the rest of eternity. Until there's a specific algorithm in play, it's hard to discuss the real repercussions, but it's a generally good policy to leave the repetitions and permutations up to the design of algorithm rather than trying to help out. Many hash algorithms already have cycles of repetition and recursion that mean the user has no reason to do more.