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 Session Fixation?
Session fixation tricks a victim into using an attacker-known session ID, so logging in hands the attacker an authenticated session too.
Chisato · · 4 min read What Is IDOR? Insecure Direct Object References Explained
IDOR is an access control flaw where an app trusts a user-supplied ID to fetch a record without checking the requester actually owns it.
Chisato · · 4 min read The OAuth PKCE Flow Explained
PKCE hardens the OAuth authorization code flow against interception, and is now recommended for every client type, not just mobile and single-page apps.