RestingOwl owl logo RestingOwl

Password Hashing Explained:bcrypt vs Argon2

Quick Answer: Never store passwords as plain text or with a fast hash like MD5 or SHA-256. Use a slow, salted password hashing function that is built to resist cracking: Argon2id is the current first choice, and bcrypt is a solid, widely supported alternative. These functions are deliberately slow and memory-hungry, so even if your database leaks, an attacker cannot reverse the hashes back into passwords at scale. Add a unique salt per password (these functions do it for you) and tune the cost so one hash takes around 250 to 500 milliseconds.

When your database leaks, and you should assume one day it will, the difference between a minor incident and a disaster is how you stored the passwords. Store them well and the stolen file is nearly useless. Store them badly and attackers crack millions of accounts in hours, then use those passwords to attack every other site your users visit. This guide explains password hashing in plain language and compares the two functions you should actually use: bcrypt and Argon2.

Why Can You Not Just Store Passwords Directly?

Storing passwords as plain text means anyone who reads your database sees every password instantly. That includes attackers, but also employees and anyone who gets hold of a backup. The fix is hashing: run the password through a one-way function and store only the result. You can check a login by hashing the entered password and comparing, but you can never turn the stored hash back into the original. The catch is that not all hashes are suitable. Fast hashes designed for files, like MD5 and SHA-256, are the wrong tool for passwords.

Why Are MD5 and SHA-256 Wrong for Passwords?

MD5 and SHA-256 were built to be fast, which is exactly the problem. Modern hardware can compute billions of SHA-256 hashes per second, so an attacker with a leaked database simply hashes every common password and every entry in a wordlist and compares. Speed that is a feature for checksums is a gift to a password cracker. Password hashing functions do the opposite on purpose: they are slow and use a lot of memory, so each guess costs the attacker real time and hardware. That is the whole idea behind bcrypt and Argon2.

ApproachSafe for Passwords?Why
Plain textNoAnyone who reads the database sees every password.
MD5 / SHA-1 / SHA-256NoToo fast. Attackers crack billions of guesses per second.
bcryptYesDeliberately slow with a tunable cost factor. Widely supported.
Argon2idYesSlow and memory-hard. Resists GPU and custom-hardware cracking.

What Is a Salt and Why Does It Matter?

A salt is a random value added to each password before hashing, and stored alongside the hash. Its job is to make sure two users with the same password get different hashes. Without salts, an attacker can precompute a giant lookup table (a rainbow table) once and use it against every leaked database, and can instantly see which users share a password. With a unique salt per password, those shortcuts stop working: the attacker has to crack each account separately. The good news is that bcrypt and Argon2 generate and store the salt for you, so you do not manage it by hand. Just never reuse a salt and never use a fixed one.

bcrypt vs Argon2: Which Should You Choose?

Both are correct choices that OWASP recommends. The difference is how they make cracking expensive and how you tune them.

FeaturebcryptArgon2id
Slows attackers byCPU cost (a work factor)CPU and memory cost together
Resists GPU crackingSomewhatStrongly, because memory is hard to parallelise
Tuning knobsOne (cost factor)Three (memory, iterations, parallelism)
Password length limit72 bytes, longer input is truncatedNo practical limit
Best forMature ecosystems and wide library supportNew projects wanting the strongest default

The practical guidance: choose Argon2id for a new project if a well-maintained library is available for your language. Choose bcrypt if you want the most battle-tested and universally supported option, or if you are already using it. Both are far better than any fast hash. One bcrypt gotcha to know: it only reads the first 72 bytes of the input, so very long passwords are silently cut. A common fix is to pre-hash long inputs, or simply prefer Argon2id where length matters.

How Do You Hash a Password in Code?

You should almost never implement the algorithm yourself. Use the vetted library for your language and let it handle the salt and encoding. Here is the shape of it in Node.js.

// Argon2id: sign up, then verify at login
import argon2 from 'argon2';

// On signup: hash and store the result (salt is included)
const hash = await argon2.hash(password, { type: argon2.argon2id });

// On login: verify the entered password against the stored hash
const ok = await argon2.verify(hash, enteredPassword);
if (ok) allowLogin();

How Do You Store Passwords Safely: OWASP Checklist

Password Storage Checklist
  1. 1Use Argon2id or bcrypt, never MD5, SHA-1, SHA-256, or plain text.
  2. 2Let the library generate a unique salt per password and store it with the hash. Never reuse or hardcode a salt.
  3. 3Tune the cost so a single hash takes roughly 250 to 500 milliseconds on your production hardware.
  4. 4For bcrypt, remember the 72-byte input limit and pre-hash longer passwords if needed.
  5. 5Block breached and common passwords at signup with the HaveIBeenPwned Pwned Passwords API before you hash.
  6. 6Rehash and upgrade a user's password to stronger parameters the next time they log in successfully.
  7. 7Store the hash in a database column that is never returned by default queries or logged.
  8. 8Consider adding a server-side secret (a pepper) held outside the database for defence in depth.

What Does OWASP Say About Password Storage?

The OWASP Password Storage Cheat Sheet is direct: use Argon2id as the first choice, with parameters of at least 19 MiB of memory, and fall back to bcrypt with a work factor of 10 or more where Argon2 is not available. It stresses salting every password, tuning the cost to your hardware, and rehashing when you raise the parameters. NIST guidance aligns with this and adds that you should check new passwords against breach corpora and avoid forced periodic resets. In short: a slow salted hash plus breached-password blocking is the baseline for storing credentials safely.

Related reading: Safe storage is one half of protecting credentials. Pair it with defences against the three password attacks, multi-factor authentication, and breached-password checks with HaveIBeenPwned. OwlAuth handles secure password hashing and breached-password blocking for you.

References

  1. 1OWASP Password Storage Cheat Sheet
  2. 2NIST SP 800-63B: Digital Identity Guidelines
  3. 3RFC 9106: Argon2 Memory-Hard Function

Q&A Section

Store passwords with a slow, salted password hashing function. Argon2id is the current first choice, and bcrypt is a strong, widely supported alternative. Both are deliberately slow so that a stolen database cannot be cracked at scale. Never store plain text and never use fast hashes like MD5 or SHA-256, which attackers can crack billions of times per second.
Yes. bcrypt remains a secure and OWASP-recommended choice as long as you set a work factor of 10 or higher and tune it so each hash takes a few hundred milliseconds. Its main limitations are that it only hashes the first 72 bytes of the input and that it resists GPU cracking less strongly than Argon2id. For new projects Argon2id is preferred, but bcrypt is far from broken.
bcrypt slows attackers down mainly through CPU cost set by a single work factor. Argon2, specifically the Argon2id variant, uses both CPU and large amounts of memory, which makes it much harder to crack on GPUs and custom hardware because memory is expensive to parallelise. Argon2id has more tuning options and no short input limit, so it is the stronger default for new systems.
No. Modern bcrypt and Argon2 libraries generate a unique random salt for each password and store it inside the resulting hash string automatically. You should never create a fixed salt, reuse one, or store salts separately by hand. Just call the library's hash and verify functions and it handles salting correctly for you.
No. Even with a salt, SHA-256 is far too fast for passwords. A salt stops precomputed rainbow tables and shared-password shortcuts, but it does nothing about raw speed, so an attacker can still make billions of guesses per second against each account. Use a purpose-built slow function like Argon2id or bcrypt, which combine salting with deliberate slowness.
Copied!