What is wrong with simple concatenation in salted password hashes? I once heard that if implementing a password hashing scheme, simply concatenating the password and salt together before hashing could lead to some subtle vulnerabilities, and I'm trying to figure out if this is actually the case.

According to this anecdote, the following should not be used:

hash(salt + password)
hash(password + salt)


And instead, something similar to an HMAC, like the following, should be used:

hash(hash(password) + salt)


Even though simple concatenation is vulnerable to length extension, I can't think of a way that could ever be useful to an attacker.

Is there any merit to this anecdote?
One difference is that with simple concatenation two different salt-password pairs could lead to the same hash input. For example: H('abcd'||'example@example.com') = H('abcde'||xample@example.com). This cannot happen between your password hashes if you use a constant width salt, but could still happen between your password hashes and those of some other service using the same hash.

With HMAC or another construction that handles the password and salt differently you avoid such collisions. Collisions still exist, of course, if the number of possible passwords and salts is long enough, but they never happen in practice.

If you generate a long enough random salt you avoid the issue even with simple concatenation, and that is usually a good idea in any case, so the practical effect may not be significant in many applications.

In practice the issue should not crop up other than in algorithm design. The best practice is always to use a real password-hashing function, such as bcrypt, scrypt or argon2, and those expose an iterface where you input the salt and password separately.