Password Hashing Explained: Salting, Bcrypt, and Argon2
Password hashing turns a password into a one-way, salted digest so a stolen database doesn't hand over credentials. How bcrypt and Argon2 work.
Password hashing is the practice of running a user’s password through a deliberately slow, one-way function before storing it, so that even if an attacker steals the database, they get a field of unusable digests instead of usable credentials. Done right, it turns a catastrophic breach into an expensive, time-consuming cracking problem instead of an instant credential leak.
Why hashing, not encryption
Passwords should never be stored in plaintext, and they shouldn’t be encrypted either — see hashing vs encryption for the full distinction. Encryption is reversible by design: whoever holds the key can recover the original password, which means the server (or anyone who compromises it) can too. Hashing is one-way. A well-designed password hash function takes the password and produces a fixed-length digest that cannot practically be reversed back into the original text. Verification works by hashing the login attempt and comparing digests, never by decrypting anything.
The problem with a plain hash
A fast general-purpose hash like SHA-256 is a poor choice for passwords, even though it’s a fine choice for things like file integrity checks. Two problems stand out:
- Speed is the enemy. SHA-256 is designed to be fast — billions of hashes per second on commodity hardware. That’s great for checksums and terrible for passwords, because it means an attacker with a stolen hash database can try billions of guesses per second per GPU.
- Identical passwords produce identical hashes. Without anything else added, two users who both pick “password123” get byte-identical hash values. An attacker can precompute a rainbow table — a lookup of hash values for common passwords — and reverse huge swaths of a stolen database in one pass, without cracking anything individually.
Salting: defeating precomputation
A salt is a random value generated per password and stored alongside the hash (it doesn’t need to be secret). The salt is concatenated with the password before hashing, so hash(password + salt) differs for every user even when the underlying password is identical. This defeats rainbow tables outright — an attacker would need a separate precomputed table per salt, which is infeasible at scale — and it means cracking one password reveals nothing about any other, even identical, password in the database. Every modern password hashing library generates and stores the salt automatically; you should never need to manage it by hand.
Purpose-built slow hashes: bcrypt, scrypt, Argon2
Salting alone doesn’t fix the speed problem, so password hashing relies on algorithms designed to be deliberately, tunably slow:
- bcrypt (1999) wraps the Blowfish cipher in a structure with a configurable cost factor that exponentially increases the number of internal rounds. It has decades of production hardening behind it and remains a safe, conservative default.
- scrypt (2009) adds a memory-hardness requirement on top of CPU cost, making it expensive to parallelize on GPUs and ASICs, which are cheap at raw compute but comparatively starved for memory bandwidth.
- Argon2 (2015) won the Password Hashing Competition and is the current recommended default for new systems. It comes in three variants — Argon2d (maximizes resistance to GPU cracking), Argon2i (resists side-channel timing attacks), and Argon2id (a hybrid, and the generally recommended choice) — and exposes independent time, memory, and parallelism cost parameters.
Comparing the options
| bcrypt | scrypt | Argon2 | |
|---|---|---|---|
| Released | 1999 | 2009 | 2015 |
| Memory-hard | No | Yes | Yes |
| Tunable parameters | Cost factor only | Cost, memory, parallelism | Time, memory, parallelism |
| GPU/ASIC resistance | Moderate | Strong | Strongest |
| Track record | Very long | Long | Growing |
| Recommended for new systems | Acceptable | Acceptable | Preferred |
All three beat a plain fast hash by orders of magnitude. If you’re maintaining a legacy system already on bcrypt, migrating isn’t urgent; if you’re starting fresh, Argon2id is the better default.
Tuning the cost factor
Every one of these algorithms exposes a work factor, and it should be tuned to your hardware, not left at a library default forever. The general approach: pick a cost parameter that takes somewhere around a few hundred milliseconds to hash on your production servers. That’s imperceptible to a real login flow but expensive to brute-force at scale, since the attacker pays that same cost per guess. As hardware gets faster, the cost factor should periodically go up — most libraries support re-hashing on a user’s next successful login when the stored parameters are below the current target, so you can ratchet up cost without forcing a mass password reset.
Hashing fits into a larger auth picture
Password hashing protects one specific failure mode: a stolen database. It doesn’t protect against phishing, credential stuffing across sites, or a compromised client. That’s why it’s usually paired with multi-factor authentication, rate-limited login endpoints, and increasingly with passkeys, which sidestep the password entirely in favor of public-key cryptography. If your application issues session tokens after login, see what a JWT is for how that credential is typically structured once the user is authenticated.
The takeaway
Password hashing is deliberately slow and deliberately salted: the salt defeats precomputed rainbow tables, and the slowness makes brute-forcing expensive even after a breach. Use a purpose-built algorithm — bcrypt, scrypt, or preferably Argon2id — never a fast general-purpose hash like SHA-256 on its own, and tune the cost parameter to your hardware rather than trusting a default forever. It’s one layer in a defense that should also include MFA and rate limiting, but it’s the layer that decides whether a stolen database is a minor incident or a catastrophe.
Keep reading
Chisato · · 5 min read What Is DPoP? Sender-Constrained OAuth Tokens Explained
DPoP binds OAuth access tokens to a client-held key, so a stolen token is useless without the private key. How proofs work and when to use DPoP.
Chisato · · 5 min read What Is an Open Redirect? Risks and How to Prevent It
An open redirect lets attackers use your domain to send users to any URL. How open redirect vulnerabilities work, why they matter, and how to fix them.
Chisato · · 4 min read What Is a Man-in-the-Browser Attack?
A man-in-the-browser attack uses malware inside the browser itself to alter what a user sees and submits, bypassing HTTPS and session protections entirely.