Passwords are still the primary method of authentication today, in a form of something you know. Humans are lazy, often resulting in low entropy and reused passwords.
Magnitudes of research and thought have been put into protecting passwords. They are known as the simplest form of a challenge–response authentication scheme.
Because the challenge "What is your password?" is repeated, so is the response, opening the door to possible replay attacks.
Choose the Right Hashing Algorithm
Argon2 is a key derivation function, the winner of the password hashing competition and should be used for new projects. In case it isn't available, use Scrypt. Any other KDF is nonoptimal.
Argon2's "i" variant is resistant to side-channel attacks, while "d" variant is resistant to time-memory tradeoff attacks. "id" is the hybrid variant, resistant to both and suitable for most purposes.
Make sure to use "id" variant of Argon2.
Salt Hashes Properly
Salts are closely related to nonces, however, nonces are for communication protocols, not hashing. Nonces protect against replay attacks, while salts protect against precomputed hashes(aka rainbow tables). It's important to separate them, as they aren't protecting against the same things.
The only requirements of salts are to be unique to each hash and public, however, they should be unpredictable to prevent precomputing future hashes.
A cryptographically secure pseudo-random number generator output of 32 bytes will produce an unpredictable, and unique value.
"Pepper" Hashes Properly
If you've been around the internet, researching password salts, you've probably heard of the "pepper". It is a cryptographic secret(secret key), that must be unpredictable and unique to each application, used in a hash function.
Wait ... sounds a lot like an keyed-hash message authentication code, right? That's because it is. One must be extremely careful implementing "peppers" because it can lead to length extension attacks and nasty problems with Bcrypt.
Argon2 actually implements a
secret value in the official spec as an optional argument to act as a "pepper". If you're using an implementation that does not support the
secret value, encrypt the hash. If you're not using Argon2, encrypt the hash.
Do not waste your time implementing both a "pepper" and encrypting the hash. You gain no practical benefit. And you might want to store your cryptographic secrets in a hardware security module.
Use a Reasonable Policy
- Maximum length of no less than 128 characters.
- Minimum length of 12 or 16 characters.
- Support almost if not all unicode and whitespace.
- Decline known passwords via HIBP API.
- Decline passwords matching the identifier, e.g: email, username.
- Don't enforce special characters, uppercase, lowercase, symbols, etc.
- Don't truncate, sanitize, or format their passwords in any way shape or form!
- Don't prevent them from copying and pasting into the password fields.
- Don't limit what they can or cannot put into the password fields.
- Don't require password changes every so often.
What matters most, is the length and entropy. If you feel still inclined to enforce the character set above, don't. Enforce multi-factor authentication instead, their accounts will be far better with it.
If you enforce that character set, you run the risk of them writing it down, forgetting it, annoying them, or choosing a weak password. You can also cause users to write down their passwords if you enforce password changes.
Finally Encourage Good Practices
Encourage the use of diceware, passphrases, password managers (like BitWarden), and multi-factor authentication(FIDO and TOTP).
Discourage passwords with personal information like names, dates, birthdays, etc. Or sharing them if at all possible.