I've read quite a bit of the StackExchange and HackerNews debates on the user of "peppers" in password hash security. There are a number of different implementations of the idea of a pepper, ranging from an additional hardcoded salt in the code hash(password, pepper, salt), to encrypting each password hash separately, in which case the secret key is the pepper.

In the case of one of the middle ground approaches, a shared and secret pepper is included via hash(hmac(password, pepper),salt). This is necessary primarily because of many hashing algorithm's reliance on the Merkle–Damgård construction which is vulnerable to length extension attacks.

However, Argon2 is not based on a Merkle–Damgård construction and therefore this attack would not apply. In using Argon2, can the naive approach of argon2(password, pepper, salt) be used?

Additionally, the Argon2 specification Introduction seems to indicate that using HMACs, peppers, or secret keys at all is unnecessary. Is Argon2ID with strong memory, threads, and time costs enough on its own?

  • Read the length extension attack. It is not a collision attack. – kelalaka Mar 26 '19 at 20:47
  • @kelalaka So HMAC is overkill? Just hash(password + salt + pepper) should be enough? – ManRow Apr 7 '20 at 14:46

Argon2 actually allows a pepper in the algorithm itself, called the secret. This would be the ideal way to use a pepper, unfortunately most language bindings I've looked at don't expose this parameter (of the bindings listed on GitHub only 2 of them specifically mention supporting keyed hashing).

If the secret parameter can't be used, argon2(hmac(pepper, password)) is a reasonable alternative. argon2 can be applied outside hmac for simplicity, as the way it's generally used it'll give you an output that includes an encoding of the salt and options used. Using hmac outside argon2 should be comparable for security, but then you'd need to keep track of the Argon2 parameters yourself.

As for the spec seeming to indicate that a pepper isn't necessary, I assume you're referring to this:

A trivial solution for password hashing is a keyed hash function such as HMAC. If the protocol designer prefers hashing without secret keys to avoid all the problems with key generation, storage, and update, then he has few alternatives: the generic mode PBKDF2, the Blowfish-based bcrypt, and scrypt.

The way I read it, what that's saying is that a keyed hash is actually the best way to hash passwords, so long as you protect the key really well (such as using an HSM). The problem is, protecting the key is difficult (HSMs cost money, supporting a pepper with an HSM takes development time), so slow hashes like scrypt, bcrypt, and Argon2 are used instead.

What it's not saying is that using a keyed hash in addition to Argon2 is a bad idea. Even if you can't protect the key with an HSM, it can still be worthwhile if it's better protected than the password hashes. The worst passwords will always be cracked no matter how high you set the cost (and your users' patience gives you a low upper limit); a pepper can prevent that.


Edit: Strike this entire comment. My approach isn't wrong, per se, but argon2 already offers a secret parameter. Just use this.

The simplest approach to this—and the one on the strongest cryptographic footing—is (simplistically) HMAC(key, argon2(password)). This also still allows the expensive argon2 calculation to be performed client-side for server-relief situations.

  • Is the assumption here that there will be one key or many? – Prime Mar 26 '19 at 21:14
  • And shouldn't I put the HMAC "inside" of the Argon hash instead of vice versa? – Prime Mar 26 '19 at 21:20
  • Re: one key or many, it depends on your threat model but it also doesn't really matter. The primary point is that an attacker with your password database would also need your secret, which should be stored elsewhere. There are also reasons you might want to use multiple keys, but unless you know a specific reason why you would want this, you shouldn't bother with the additional complexity. – Stephen Touset Mar 26 '19 at 21:36
  • Re: inside or outside, inside prevents server relief, since argon2(HMAC(key, password)) requires the password to be HMAC'd server-side first. With HMAC(key, argon2(password)), a client can compute argon2(password) first before sending to the server. This approach also has the secondary advantage of ensuring that the server never actually sees the user's raw password, so there's no chance of it hitting a log file. – Stephen Touset Mar 26 '19 at 21:38
  • But lastly, argon2 already provides a secret parameter. Really, really, really just use this. – Stephen Touset Mar 26 '19 at 21:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.