Is there an advantage to using a hash in combination with a key-derivation function?

Note: This question was originally asked at Information Security Stack Exchange, but was moved here due to a topic overlap.

It is currently recommended by OWASP to use an adaptive key-derivation function, such as Argon2, PBKDF2, scrypt or bcrypt, when storing passwords securely.

Is there any advantage or disadvantage to combining such a key-derivation function with a traditional cryptographic hash (SHA-family, etc.) function, such as follows: $$hash,$$salt = kdf(hash($$password),$$salt).

Before writing this question, I attempted to find a solution myself. Since cryptography is somewhat famous for being difficult to "get right", I was still uncertain if my reasoning is applicable.

My reasoning was as follows:

Let's assume that the chosen hash algorithm were completely broken, meaning that an attacker could easily calculate a value1 that would match a given hash. If the used key-derivation function was secure, the attacker would not be able to gain any advantage compared to a system where the plaintext password would be directly supplied to the key-derivation function.

1: Not necessarily the original value, but a value which would result in the same hash.

• Under the hood of a password-based KDF is actally already a chain of hash functions. So basically, you're adding a single extra call to a hash function - which is probably not going to change much on the practical side of security, if the KDF is configured properly. – tylo Jan 18 '19 at 20:12

There is no weakness to pre-hashing a password if you use a proper hash function.

If the probability of a hash collision is small then hashing does not reduce a cracker's search space.

However, if the hash had a 16-bit output, then you would have a problem. A cracker would just guess-and-check each 16-bit KDF input, skipping prehashing, rather than guess-and-check different passwords.

On the other hand, if you use SHA-512 then you are not reducing a password cracker's search space. Hash functions aren't truly one-to-one functions, but we can safely say we have never hashed two distinct inputs that have the same SHA-512 output. You would expect humanity to have to hash around $$2^{256}$$ different messages (give or take slightly on that exponent) before it becomes probable that we would have two messages collide by chance. (That $$2^{256}$$ makes astronomically large numbers look infinitesimal by comparison.)

Until we generate that many distinct passwords we can get away with approximating the relationship between prehashed "passwords" and pre-prehashed passwords as a one-to-one mapping. That makes the prehashed password basically equivalent to the user's password. (Just in a different format.)

Guessing random 256-bit strings isn't a shortcut or a smart strategy. It still makes sense to guess and check plausible passwords by brute force. Again, the cracker's search space isn't reduced, so there is should be no impact on security.

Note that I said a hash with a low collision probability would be safe (but still pointless). You don't actually need collision resistance. If you used SHA-1 instead, that collision attack wouldn't necessarily have any impact on security. In most real world scenarios that would just mean a user could use more than one password to login to the same account or decrypt the same files. User chosen passwords or randomly generated passwords still won't result in SHA-1 collisions in non-adversial settings.

Aside: The security of password hashes and KDFs in general don't rely on collision resistance. You just need a function that is preimage resistant and that does not have any biases that reveal information about inputs. (Actually, for KDFs at least, biased output in general.)

A hash that wasn't preimage resistant would not work for user authentication. (A hacker could find a password that hashes to the same value. It wouldn't matter to him whether it was the same as the original password.)

A hash with biases might make a password cracker's search easier.

Password based KDFs like Argon2 utilize hash algorithms with these requirements.

Finally there are two scenarios where prehashing passwords might help. Just not for reasons based in cryptography.

First, there might be quirks in a KDF or password hash. For example, there might be a length limit on the password (as in bcrypt). Prehashing transforms the user input into a fixed length bit string.

Bcrypt has several issues related to user input. If you had to use bcrypt it would be a good idea to prehash a user's password with a 256-bit hash and encode the output using base64. That results in a pure ASCII string with no stray null characters that's small enough to be used with existing bcrypt implementations. (You should still use Argon2 because it's much better for password hashing.)

Second, you may opt for the client to prehash users' passwords before sending them to the server. Web applications typically receive the user's plaintext password, whether they use HTTP or HTTPS. Sending a hash of the password is not a substitute for encryption (because eavesdroppers can just send the same hash without ever requiring someone to log in with the actual password), but it potentially obscures the user's actual password.

If the user's password is weak then the server can guess the original just the same as any other password cracker would. If a password is inhumanly strong then the server won't ever know the user's actual password.

(Although that's unrealistic. If passwords are assumed to be that strong then there isn't the need to use a dedicated password hashing algorithm instead of an inexpensive hash. It's likely better to do client-side password stretching for non-web-applications if the server shouldn't be trusted with actual passwords. Or use an alternative authentication method.)

• A third scenario where pre-hashing is useful: upgrading your password hashing scheme in-place. Say you have an older app which stores passwords as SHA1(salt,password). If you make your new password hash Argon2id(salt,SHA1(salt,password)) you can upgrade all the password hashes without waiting for users to log in again. I’ve used this approach successfully on high-scale apps with 1M+ users, upgrading hashes as in small batches in the background over the course of several days with zero downtime. – rmalayter Jan 20 '19 at 13:30