Safeguard
Security

Password Storage Best Practices Every Developer Should Follow

Storing passwords safely comes down to one rule: never store the password. Here are the password storage best practices that actually hold up against modern attacks.

Karan Patel
Platform Engineer
5 min read

The core of password storage best practices is a rule that sounds strange the first time you hear it: never store the password. You store a slow, salted, one-way hash of it, and at login you hash the incoming attempt and compare. If your database leaks, attackers get hashes they still have to crack rather than a ready-made list of credentials.

Almost every catastrophic password breach traces back to violating this. Plaintext storage, reversible encryption, or a fast general-purpose hash like MD5 or SHA-256 with no salt all leave you one database dump away from disaster.

Hashing is not encryption

This trips up a lot of teams, so it is worth stating plainly. Encryption is reversible: with the key you get the original back. That is exactly what you do not want for passwords, because whoever holds the key holds every password.

A password hash is one-way. There is no key and no decrypt function. Verification works by hashing the candidate and comparing digests. When someone says "we encrypt our passwords," that is usually a red flag — either they mean hashing (good, wrong word) or they literally encrypt (bad, right word).

Use a purpose-built password hash

General-purpose hashes like SHA-256 are designed to be fast, which is the opposite of what you want. Attackers with a GPU can try billions of SHA-256 guesses per second. Password hashing functions are deliberately slow and memory-hard so that each guess costs real time and RAM.

The current recommendations, in rough order of preference:

  • Argon2id — the modern default and winner of the Password Hashing Competition. Tunable across memory, iterations, and parallelism.
  • scrypt — memory-hard and well regarded where Argon2 is unavailable.
  • bcrypt — older but still solid, with a well-understood cost factor. Note its input is effectively capped around 72 bytes.
  • PBKDF2 — acceptable when you need a FIPS-validated option, though it is not memory-hard.

Here is Argon2id in Python:

from argon2 import PasswordHasher

ph = PasswordHasher()  # sane defaults for memory, time, parallelism
digest = ph.hash("correct horse battery staple")

# at login
try:
    ph.verify(digest, submitted_password)
    # optionally re-hash if parameters have since increased
    if ph.check_needs_rehash(digest):
        digest = ph.hash(submitted_password)
except Exception:
    reject_login()

Salting and why it is automatic now

A salt is a unique random value added to each password before hashing. It ensures two users with the same password get different hashes, and it defeats precomputed rainbow tables. The good news: modern libraries like bcrypt and Argon2 generate and embed a random salt for you inside the output string. You do not manage salts manually anymore, and you should not try.

A pepper — a secret value applied to every hash and stored separately from the database, for example in a key management service — adds defense in depth. It is optional and only helps if it lives somewhere the database dump would not include.

Tune the work factor, then leave it alone until it is fast again

Every slow hash has a cost parameter. For bcrypt it is the cost factor; for Argon2 it is memory, iterations, and parallelism. Set them so that hashing takes roughly 250 to 500 milliseconds on your production hardware. That is fast enough that users do not notice login latency, and slow enough that mass cracking becomes expensive.

Revisit these parameters yearly. Hardware gets faster, so the cost that was painful for attackers in 2023 is cheaper now. When you raise the parameters, re-hash a user's password transparently on their next successful login.

Things that quietly ruin good hashing

Even with the right algorithm, these mistakes undo the protection:

  • Truncating or transforming the password before hashing — do not lowercase it, do not strip characters.
  • Logging the plaintext — passwords leaking into application logs is a shockingly common finding.
  • Low, arbitrary length caps — allow long passphrases; a cap of eight characters is a defect, not a feature.
  • Rolling your own hash scheme — use the vetted library. Cleverness here is a liability.
  • Ignoring the crypto in your dependencies — an outdated hashing library can carry its own weaknesses. An SCA tool can flag when a password-hashing dependency is behind on security fixes.

Beyond hashing

Storage is one layer. Round it out with rate limiting on login attempts, a check against known-breached password lists (the k-anonymity API from Have I Been Pwned works well without sending full passwords), and multi-factor authentication. MFA means that even a cracked password is not enough to take over an account. Our security academy walks through building a full authentication flow with these controls.

FAQ

Should I use bcrypt or Argon2?

For new systems, Argon2id is the recommended default because it is memory-hard and highly tunable. bcrypt remains perfectly acceptable and battle-tested. Both are far better than any general-purpose hash. The wrong choice is SHA-256, MD5, or plaintext.

Do I still need to add a salt manually?

No. Modern libraries generate a unique random salt per password and store it inside the resulting hash string. Manually managing salts is an outdated practice that only introduces bugs.

What length should I allow for passwords?

Set a generous maximum — 64 characters or more — so users can use passphrases. Enforce a minimum of at least 8, ideally 12. Never impose a low maximum or forbid special characters; both weaken security and frustrate users.

Is it safe to encrypt passwords instead of hashing them?

No. Encryption is reversible, so anyone who obtains the key gets every password in plaintext. Passwords must be stored using a one-way slow hash. Reserve encryption for data you genuinely need to read back later.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.