A credential store should be designed on the assumption that it will eventually be exposed, through a SQL injection, a misconfigured backup, an insider, or a compromise of the host, and the only question that then matters is what an attacker can do with the file once they hold it. That outcome is decided entirely by how the passwords were stored. The first instinct, to encrypt them, is the wrong one, because encryption is reversible by definition and merely relocates the problem to protecting the key, which becomes a single point whose compromise exposes every password at once. Hashing is the correct primitive because it runs one way. The server never needs the original password back, it needs only to confirm that a submitted password produces the same stored digest, so a function that cannot be reversed is exactly what the task requires.
Two properties separate a safe password hash from a dangerous one. The first is a per user salt, a unique random value stored alongside the digest, which ensures that identical passwords produce different hashes and defeats precomputed lookups such as rainbow tables, forcing an attacker to attack each account on its own. The second is deliberate cost. General purpose digests such as MD5, SHA-1, and SHA-256 are engineered to be fast, which is precisely the wrong property here, because commodity hardware evaluates billions of SHA-256 candidates per second and a stolen fast hash falls quickly to a dictionary or brute force run. Password hashing functions are intentionally slow and, in the modern designs, memory hard, governed by a work factor that is raised as hardware improves, so that each guess remains expensive enough to make large scale cracking uneconomic.
The choice of function is narrower than the field of options suggests, and the sensible defaults are well established.
| ALGORITHM | NOTES |
|---|---|
| Argon2id | The current recommendation, memory hard and resistant to both GPU and side channel attack. Tune its memory, iteration, and parallelism parameters to the hardware, raising memory as high as latency allows. |
| bcrypt | Long proven and widely available, driven by a cost factor, where a value of 12 or above is reasonable today. Note the input limit near 72 bytes. |
| scrypt | Memory hard and a solid choice where Argon2 is unavailable, tuned through its cost and block size parameters. |
| PBKDF2 | Acceptable where a standard mandates it, but it is not memory hard, so only a high iteration count stands between the hash and a fast attack. |
Beyond the algorithm, two operational details separate a careful implementation from a fragile one. A pepper, a secret value mixed into every hash and stored apart from the database in application configuration or a key management service, means that a disclosure of the database alone does not yield crackable hashes, because the secret needed to reproduce them was never in the file. And verification must use a constant time comparison, because a naive byte by byte check leaks information through timing that can be used to recover the digest. The working policy is to hash with Argon2id where possible and bcrypt where not, let a vetted library manage per user salts, decline to truncate the password before hashing beyond the algorithm's own limit, raise the work factor over time and transparently rehash on the next successful login, and never write the plaintext to a log under any circumstance. Storing passwords well is not a hard problem in the sense of requiring novel cryptography, it is a solved problem that continues to be gotten wrong, and the distance between a breach that forces a password reset and one that hands an attacker every account is nothing more than the discipline to use the right function correctly.