Never store passwords in plaintext or use fast hashing
Passwords must never be stored in plain text. Fast hashing algorithms such as SHA-256 are not suitable for password storage because they allow attackers to perform large numbers of guesses quickly.
Argon2id minimum configuration
Use Argon2id with a minimum configuration of 19 MiB of memory (m=19456), an iteration count of 2 (t=2), and 1 degree of parallelism (p=1).
scrypt minimum configuration
If Argon2id is not available, use scrypt with a minimum CPU/memory cost parameter of 2^17 (128 MiB), a minimum block size of 8 (1024 bytes), and a parallelization parameter of 1.
bcrypt legacy systems configuration
For legacy systems using bcrypt, use a work factor of 10 or more and enforce a maximum password input limit of 72 bytes.
PBKDF2 FIPS-140 compliance configuration
If FIPS-140 compliance is required, use PBKDF2 with a work factor of 600,000 or more iterations and set the internal hash function to HMAC-SHA-256.
Pepper as defense in depth
Consider using a pepper to provide additional defense in depth, though alone it provides no additional secure characteristics.
Salt requirements for password hashing
A unique salt must be added to each password to prevent attackers from using precomputed lookup tables like rainbow tables. Modern password hashing functions such as Argon2id, bcrypt, and PBKDF2 automatically generate and manage salts internally when libraries are used correctly.
Hashing is one-way, encryption is two-way
Hashing is a one-way function and is the most appropriate approach for password validation. Encryption is a two-way function and should not be used for passwords except in edge cases where the application must obtain the original plaintext password to authenticate with another system.
Argon2id parameters equivalence
The following Argon2id configurations provide equal levels of defense with trade-offs between CPU and RAM usage: m=47104 (46 MiB) t=1 p=1; m=19456 (19 MiB) t=2 p=1; m=12288 (12 MiB) t=3 p=1; m=9216 (9 MiB) t=4 p=1; m=7168 (7 MiB) t=5 p=1.
scrypt parameters equivalence
The following scrypt configurations provide similar minimal levels of defense with trade-offs between parallelism and RAM usage: N=2^17 (128 MiB) r=8 p=1; N=2^16 (64 MiB) r=8 p=2; N=2^15 (32 MiB) r=8 p=3; N=2^14 (16 MiB) r=8 p=5; N=2^13 (8 MiB) r=8 p=10.
PBKDF2 iteration counts by algorithm
PBKDF2 iteration counts should be set based on the internal hashing algorithm: PBKDF2-HMAC-SHA256 requires 600,000 iterations (recommended); PBKDF2-HMAC-SHA512 requires 220,000 iterations; PBKDF2-HMAC-SHA1 requires 1,400,000 iterations but is legacy only and disallowed by NIST SP 800-131A Rev. 2 for new use after 2030.
Parallel PBKDF2 cost parameters
Parallel PBKDF2 configurations use: PPBKDF2-SHA512 with cost 2; PPBKDF2-SHA256 with cost 5; PPBKDF2-SHA1 with cost 10. These settings are equivalent in defense provided.
Pepper storage requirements
A pepper should be shared between stored passwords (unlike salt which is unique to each password), must not be public, and must not be stored along with the generated hash. Peppers should be stored in secrets vaults or Hardware Security Modules (HSMs) separately from the password database.
Pre-hashing pepper strategy
In the pre-hashing pepper strategy, a pepper is added to a password before being hashed by a password hashing algorithm. The pepper should be a random value generated securely.
Post-hashing pepper strategy with HMAC
In the post-hashing pepper strategy, a password is hashed using a password hashing algorithm, then the resulting hash is hashed again using an HMAC (such as HMAC-SHA256 or HMAC-SHA512) before storing in the database. The pepper acts as the HMAC key and should be generated according to HMAC algorithm requirements.
Work factor purpose and tuning
The work factor is the number of iterations of the hashing algorithm performed for each password, typically 2^work iterations, and is stored in the hash output. It makes calculating the hash more computationally expensive, reducing the speed and cost for attackers attempting to crack hashes. As a general rule, calculating a hash should take less than one second, requiring experimentation on the specific server to determine the optimal work factor.
Upgrading work factor over time
Work factors can be increased over time as hardware becomes more powerful and cheaper. The most common approach is to wait until the user next authenticates, then re-hash their password with the new work factor. Different hashes will have different work factors and may never be upgraded if the user does not log back in. It may be appropriate to remove older password hashes and require users to reset passwords to avoid storing less secure hashes.
Argon2id is password hashing competition winner
Argon2 won the 2015 Password Hashing Competition. The Argon2id variant should be used because it provides a balanced approach to resisting both side-channel and GPU-based attacks.
bcrypt null byte truncation vulnerability
When pre-hashing passwords with bcrypt using a fast algorithm like SHA-2 or HMAC, null bytes in the hash output can cause truncation. bcrypt expects a null-terminated password string and will only use the hash value up to the first null byte, potentially equating bcrypt(H($password)) to bcrypt("") if H($password)[0] == 0. This can be avoided by encoding the hash value to a printable string with base64.
Password shucking attack with bcrypt
Password shucking uses the fact that it is easy to check if bcrypt(base64(H($password))), $salt, $cost) equals a known leaked hash. If the inner hash function H is used with the same password elsewhere and known to an attacker, cracking the password can be reduced to breaking the hash function H. This only works if a leaked hash is known to the attacker through a breach database or rainbow tables. Using pure SHA-512 (i.e., bcrypt(base64(sha512($password)))) is dangerous and as secure as just using SHA-512 alone. To mitigate password shucking, use bcrypt(base64(hmac-sha384(data:$password, key:$pepper))) and store the pepper outside the database.
bcrypt base64 encoding length consideration
base64 increases the length of hash values, potentially above the 72-byte bcrypt limit for large hashes like SHA-512, resulting in truncation. However, this truncation is negligible.
Modular PHC string format for algorithm versioning
Use the modular PHC string format to store the password hashing algorithm and work factor with the password. This makes upgrading the hashing algorithm easier and allows for a mix of old and new algorithms during transition periods.
PBKDF2 pre-hashing with long passwords
When PBKDF2 is used with HMAC and the password is longer than the hash function's block size (64 bytes for SHA-256), the password is automatically pre-hashed. Good implementations perform pre-hashing before the expensive iterated hashing phase. However, some implementations perform the conversion on each iteration, which can make hashing long passwords significantly more expensive than hashing short passwords, potentially creating a denial of service vulnerability. Manual pre-hashing with added salt can reduce this risk.
Password hashing library Unicode support
Password hashing libraries must be able to accept a wide range of characters and be compatible with all Unicode codepoints so users can use the full range of characters available on modern devices, especially mobile keyboards. Libraries should support passwords from various languages and include pictograms. Libraries must be able to use input that may contain NULL bytes and should not reduce entropy prior to hashing.
Pepper compromise requires password reset
If a pepper is compromised, it must be changed. However, peppers cannot be changed without knowledge of a user's password. Therefore, changing a pepper requires forcing all users whose passwords were protected by the previous pepper to reset their passwords.
bcrypt should only be used for legacy systems
bcrypt password hashing function should only be used for password storage in legacy systems where Argon2 and scrypt are not available.
Modern algorithm parameters are publicly safe
Application developers do not need to hide which password hashing algorithm is used. If utilizing a modern password hashing algorithm with proper configuration parameters, it is safe to state publicly which algorithms are in use.
Upgrading from legacy password hashing algorithms
To upgrade from older algorithms like MD5 or SHA-1 to modern password hashing algorithms, there are two primary approaches:
**Method one: Expiration and forced reset** — Expire and delete password hashes of inactive users and require them to reset passwords to login again. This is secure but not user-friendly.
**Method two: Layering hashes** — Use existing password hashes as inputs for a more secure algorithm, such as bcrypt(md5($password)). This avoids needing the original password but can make hashes easier to crack. These layered hashes should be replaced with direct hashes of users' passwords the next time they log in.