Safeguard
Vulnerabilities

Password Security Storage: Hashing Done Right

Password security storage still gets built wrong in 2024 — here's what a correct implementation looks like, from algorithm choice to salt handling to migration.

Safeguard Research Team
Research
5 min read

Password security storage mistakes are still one of the most preventable causes of mass credential exposure, and the failure mode is rarely "no hashing at all" anymore — it's using the wrong kind of hash. A fast, general-purpose hash like MD5 or unsalted SHA-256 can be brute-forced against a leaked database at billions of guesses per second on commodity GPU hardware, which means a breach that should have cost attackers years of computation instead costs them an afternoon. The fix is a purpose-built password hashing algorithm — bcrypt, scrypt, or Argon2 — combined with correct salting, and it's been a solved problem for over a decade, which makes it more frustrating every time a breach disclosure reveals a company still wasn't using one.

Why can't you use SHA-256 or MD5 for password storage?

You can't because those algorithms are designed to be fast, and speed is the opposite of what you want when hashing a password. SHA-256 is built for verifying file integrity or signing data, workloads where computing millions of hashes per second is a feature; applied to password storage, that same speed lets an attacker with a leaked hash database run billions of guesses per second on GPU hardware, cracking the vast majority of real-world passwords (which cluster heavily around a few thousand common patterns) within hours. MD5 is worse still — it's also cryptographically broken for collision resistance, though that specific weakness isn't the main problem for password storage; the speed problem is. Purpose-built password hashing functions deliberately slow down verification by orders of magnitude, and some allow tuning that slowdown as hardware gets faster.

What do bcrypt, scrypt, and Argon2 actually do differently?

They deliberately impose a configurable computational and, in some cases, memory cost, so that computing one hash takes a meaningful fraction of a second rather than a microsecond, which is fine for a legitimate login but devastating for an attacker trying billions of guesses. Bcrypt, based on the Blowfish cipher, has been battle-tested since 1999 and remains a solid, widely supported default. Scrypt adds a memory-hardness requirement specifically to resist GPU and ASIC-based cracking, which are fast at raw computation but comparatively expensive at high-memory operations. Argon2, the winner of the 2015 Password Hashing Competition, is the current recommended default in most modern guidance (including OWASP's) because it lets you tune memory cost, time cost, and parallelism independently, giving the most flexibility to match your hashing cost to your hardware and threat model.

What role does salting play, and do these algorithms handle it automatically?

Salting adds a unique, random value to each password before hashing, which prevents attackers from using precomputed rainbow tables and, critically, ensures that two users with the identical password produce completely different hash outputs. Bcrypt, scrypt, and Argon2 all handle salt generation and storage automatically as part of their standard implementation — the salt is embedded in the output hash string itself, so you don't need to manage it as a separate database column or worry about generating it yourself with a weak random source. If you ever see a custom password hashing scheme that manages its own salt logic by hand, that's a strong signal to replace it with a standard library implementation instead of trusting the custom code.

How do you migrate a legacy password database to a modern hash?

You migrate incrementally, re-hashing each user's password with the new algorithm the next time they successfully log in, since you can't re-hash a password you don't have in plaintext. On login, verify against the old hash first; if it matches, immediately compute and store the new hash (bcrypt, scrypt, or Argon2) and discard the old one. For users who don't log in during the migration window, you're left with a choice: force a password reset after a deadline, or accept that some accounts remain on the legacy hash indefinitely — most security teams choose the forced reset for anything storing sensitive data. This pattern avoids ever needing to touch plaintext passwords during migration, which should never exist in your system in the first place.

Where does this fit into a broader application security program?

Password storage is a narrow but high-severity slice of a much bigger authentication security surface, and it's exactly the kind of hardcoded-pattern issue that static analysis tools are well-suited to catch automatically — flagging any call to a fast general-purpose hash function in an authentication code path before it ships, rather than relying on someone to notice during code review. Pair that automated check with a periodic audit of your dependency tree (via SCA) to confirm you're using an actively maintained, non-deprecated password hashing library, since even purpose-built algorithms need library updates as cryptographic guidance evolves.

FAQ

Is bcrypt still considered secure in 2024?

Yes — bcrypt remains a solid, widely recommended choice, though OWASP's current guidance favors Argon2id as the primary default when available, with bcrypt and scrypt as acceptable alternatives.

What "cost factor" should I use for bcrypt or Argon2?

Tune it so hashing takes roughly 250-500 milliseconds on your production hardware — high enough to meaningfully slow brute-force attempts, low enough not to degrade legitimate login latency; re-evaluate as hardware improves.

Does encrypting passwords instead of hashing them work?

No — encryption is reversible by design (with the right key), while password storage should be one-way; if your database or key is ever compromised, encrypted passwords can be decrypted, while properly hashed and salted passwords cannot be reversed.

Should password hashing use a pepper in addition to a salt?

It can add defense in depth — a pepper is a secret value stored separately from the database (for example, in an environment variable or secrets manager) — but it's a supplementary measure, not a substitute for a proper salted, slow hash algorithm.

Never miss an update

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