The most secure password hashing algorithms
Implementing the most secure password hashing algorithms.
The Most Secure Password Hashing Algorithms
Password hashing algorithms can make or break a database breach. A strong hashing algorithm is your last line of defence when an attacker gains read access to your database — and history shows that even large, well-resourced organisations get breached.
This guide covers the three algorithms security professionals recommend today — Bcrypt, Scrypt, and Argon2 — with practical notes on parameter tuning, real-world attack benchmarks, and code you can drop into PHP, Node.js, or Python.
Why the Hashing Algorithm Matters
When a database leaks, the damage done to your users depends almost entirely on two things: the strength of their passwords, and the algorithm used to store them.
Enforcing a strong password policy — a mix of uppercase, lowercase, numbers, and special characters — is the foundation. An insecure algorithm will eventually yield even a strong password given enough computing resources; a secure algorithm buys your users time to change credentials before a cracker succeeds.
The key property that distinguishes a password hashing algorithm from a general-purpose hashing algorithm (like MD5 or SHA-1) is intentional slowness, also called a work factor or cost factor. The slower the algorithm, the fewer guesses per second an attacker can make. The algorithms below are specifically designed to be tunable: you can increase the work factor as hardware gets faster over time.
Bcrypt
Bcrypt was designed based on the Blowfish cipher and originally presented at USENIX in 1999. It is the most widely deployed password hashing algorithm in production systems today.
Bcrypt is recognisable by one of three password-hash prefixes:
$2a$– The original specification used this prefix.$2x$,$2y$– June 2011, after a bug was discovered incrypt_blowfish(the PHP implementation of Blowfish) mishandling 8th-bit characters.$2b$– February 2014, after a bug was discovered in the OpenBSD implementation when passwords exceeded 255 characters.
Cost Factor / Work Factor
The cost factor is a power-of-two multiplier on the number of internal hashing rounds. A cost of 10 means 2¹⁰ = 1,024 rounds; a cost of 12 means 2¹² = 4,096 rounds. The higher the cost, the slower the hash — and the slower the attacker.
A reasonable starting point for most servers is cost 10–12. You should benchmark your own hardware: a well-tuned Bcrypt hash should take roughly 100–300 ms on your target server. If it's faster, increase the cost; if logins feel sluggish under load, lower it.
Quick-Start Code
PHP (built-in since PHP 5.5):
Node.js (via the bcrypt npm package):
Python (via the bcrypt pip package):
Pros
Bcrypt has been heavily scrutinised over the years. Originally designed for OpenBSD — a project with a strong emphasis on security and code auditing — it has accumulated more public analysis than any other password hashing scheme. Despite a couple of implementation-level bugs (addressed by the $2y$ and $2b$ prefixes), the core algorithm has remained solid for over two decades.
Cons
Because Bcrypt was created before the GPU era, it is primarily CPU-bound and does not strongly penalise GPU-based attacks the way memory-hard algorithms do. This means well-resourced attackers can parallelise Bcrypt cracking across many GPU cores.
A real-world demonstration: at the Passwords^12 conference in Oslo, Norway, researcher Jeremi Gosney demonstrated a 25-GPU cluster (five 4U servers, each carrying five AMD Radeon GPUs, connected over 10 Gbps Infiniband) running HashCat that achieved 71,000 Bcrypt attempts per second. For comparison, the same rig hit 180 billion MD5 attempts per second — illustrating why general-purpose hash functions are entirely unsuitable for password storage.
Even so, context matters. An 8-character password using uppercase, lowercase, digits, and 19 common special characters has approximately 645 trillion possible combinations ((26+26+10+19)⁸). At 71,000 attempts/second, exhaustive search of the full keyspace would take on the order of 288 years. A strong password + Bcrypt is still a very good defence.
Bcrypt also has a 72-character input limit: bytes beyond position 72 are silently ignored. If your application supports passphrases, consider pre-hashing input with SHA-256 (and base64-encoding the result before passing to Bcrypt) or switching to Argon2.
Scrypt
Scrypt was first published by Colin Percival in 2009 and standardised by the IETF as RFC 7914 in 2016. It is also used in several cryptocurrencies as a proof-of-work function, including Litecoin and Dogecoin.
Scrypt is identifiable by the $scrypt$ password-hash prefix.
Key Parameters
Scrypt exposes three tunable parameters:
| Parameter | Meaning | Typical value |
|---|---|---|
N | CPU/memory cost (power of 2) | 16384–131072 |
r | Block size | 8 |
p | Parallelisation factor | 1–4 |
Memory usage is approximately 128 × N × r bytes. At N=16384, r=8, each hash requires around 16 MB of RAM — making large-scale GPU attacks significantly more expensive than against Bcrypt.
Pros
Scrypt builds upon Bcrypt's computational expense and adds memory hardness. Because the algorithm requires a large working memory buffer, attackers need not just fast processors but also large amounts of fast RAM per cracking thread. This increases the cost of GPU- and ASIC-based attacks considerably.
Cons
Scrypt is younger than Bcrypt and has received less public cryptanalysis. The p (parallelisation) parameter was intended to allow multi-core use, but in practice its interaction with memory hardness is less well-understood than Argon2's equivalent. It has not been adopted as widely as Bcrypt in mainstream frameworks, so library support varies more across languages and platforms.
Argon2
Argon2 is the newest of the three, having won the Password Hashing Competition (PHC) in 2015. It was designed specifically to maximise resistance to GPU, FPGA, and ASIC cracking attempts by combining both memory hardness and time hardness.
There are three variants:
| Variant | Recommended for | Notes |
|---|---|---|
$argon2d$ | Cryptocurrency / non-user-facing use | Faster, but vulnerable to side-channel attacks |
$argon2i$ | Password hashing (original recommendation) | Resistant to side-channel attacks |
$argon2id$ | Password hashing (current best practice) | Hybrid — first pass uses argon2i, remainder uses argon2d; best of both |
Note: As of Argon2 v1.3 and the current OWASP recommendation, Argon2id is the preferred variant for password hashing. It provides side-channel resistance on the first pass while gaining the stronger GPU/ASIC resistance of Argon2d in later passes. If you are starting a new project, prefer Argon2id over Argon2i.
Key Parameters
| Parameter | Meaning | OWASP minimum recommendation |
|---|---|---|
m | Memory (KiB) | 19456 KiB (≈ 19 MB) |
t | Iterations (time cost) | 2 |
p | Parallelism | 1 |
Pros
Argon2id offers the strongest theoretical resistance to modern GPU and ASIC cracking hardware. Its flexible parameters let you tune memory and CPU cost independently, which gives you fine-grained control over the hash time across different deployment environments.
Cons
Argon2 is the least battle-tested of the three. While no practical breaks of Argon2id are known, it has experienced fewer years in production than Bcrypt and has been subject to somewhat less public cryptanalysis. Two academic attacks were identified against Argon2i; Argon2i 1.3 and the move to Argon2id address the known vectors, but the algorithm's relative youth means some organisations prefer to stick with Bcrypt until Argon2 has more years in the field.
Comparison Summary
| Bcrypt | Scrypt | Argon2id | |
|---|---|---|---|
| First published | 1999 | 2009 | 2015 |
| Standardised | USENIX 1999 | RFC 7914 (2016) | PHC winner 2015 |
| Memory hard | No | Yes | Yes |
| GPU resistance | Moderate | High | Highest |
| 72-char limit | Yes | No | No |
| Maturity / scrutiny | Highest | Moderate | Lower |
| OWASP-recommended | Yes | Yes | Yes (preferred) |
General guidance:
- New projects: Argon2id with OWASP minimum parameters (m=19456, t=2, p=1), or increase memory for stronger resistance.
- Existing PHP/Node/Python projects: Bcrypt at cost 10–12 is perfectly reasonable and well-supported. Migrate to Argon2id if your framework supports it easily.
- Never use MD5, SHA-1, SHA-256, or any other general-purpose hash for passwords — they are orders of magnitude too fast for an attacker to crack.
A Note on Password Policy
A hashing algorithm is only as secure as the password behind it. An 8-character random alphanumeric password is orders of magnitude harder to crack than password123, regardless of algorithm. Enforce:
- Minimum length of 12+ characters (NIST SP 800-63B recommends allowing up to 64 characters)
- Checking new passwords against a list of known-breached passwords (e.g., the HaveIBeenPwned Passwords API)
- No forced periodic resets (they train users to choose predictable patterns)
Further Reading
If you want to see working implementations across PHP, Node.js, and Python — including how to verify stored hashes at login — see our companion tutorial: How to encrypt passwords in PHP/Node.JS/Python.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.