Why Password Hashing Matters
When users create accounts, storing passwords in plain text is a catastrophic security failure — a single database breach exposes every user's password. The solution is to store a cryptographic hash of the password instead. During login, you hash the submitted password and compare it to the stored hash. But not all hash functions are equally suited for this job.
What Is SHA-256?
SHA-256 (Secure Hash Algorithm 256-bit) is part of the SHA-2 family, designed by the NSA and standardized by NIST. It produces a 256-bit (32-byte) fixed-length output from any input. SHA-256 is:
- Deterministic — same input always produces the same output
- Fast — a modern GPU can compute billions of SHA-256 hashes per second
- Collision-resistant — finding two inputs with the same output is computationally infeasible
- One-way — you cannot reverse a hash to retrieve the original input
SHA-256 is excellent for data integrity (file checksums, digital signatures, blockchain). It is wrong for password hashing because of its speed.
The Problem with Fast Hash Functions for Passwords
Speed is the enemy when hashing passwords. An attacker who steals a database of SHA-256 password hashes can launch an offline brute-force attack:
- A modern GPU cluster can attempt 10–50 billion SHA-256 hashes per second
- An 8-character alphanumeric password has ~218 trillion combinations
- At 10 billion hashes/second, that's cracked in ~6 hours
Even with salting (adding random data per-user to prevent precomputed rainbow table attacks), SHA-256 hashing is far too fast for password storage.
What Is bcrypt?
bcrypt was designed in 1999 specifically for password hashing. Its key feature is a configurable work factor (cost factor) that makes the hashing process computationally expensive — and that cost can be increased as hardware improves.
# Example bcrypt hash
$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/OwKyA1M4JGTi2Jk.e
# Structure:
# $2b$ — bcrypt version
# $12$ — cost factor (2^12 = 4,096 iterations)
# 22 chars — 128-bit salt (Base64)
# 31 chars — 184-bit hash (Base64)
With a cost factor of 12 (a common default), bcrypt takes roughly 250ms per hash on a modern server. On the same GPU that can do 10 billion SHA-256 hashes per second, bcrypt at cost 12 yields only about 20,000–30,000 hashes per second — over 300,000× slower for the attacker.
bcrypt's Limitations
bcrypt has two notable limitations to be aware of:
- 72-byte input limit — bcrypt silently truncates inputs longer than 72 bytes. Passwords over 72 characters are not fully hashed. Some implementations pre-hash with SHA-256 and then bcrypt the result to work around this.
- No GPU resistance by design — Modern GPUs can still parallelize bcrypt more efficiently than CPUs. Argon2 (see below) was designed to address this.
Modern Alternatives: Argon2 and scrypt
Argon2 won the Password Hashing Competition in 2015 and is the current recommended choice for new systems. It has three variants:
Argon2i— optimized against side-channel attacks (use for password hashing)Argon2d— maximum GPU resistance (use for cryptocurrency)Argon2id— hybrid, recommended for most use cases
Argon2 allows tuning three parameters: time cost (iterations), memory cost (RAM required), and parallelism. The memory requirement is particularly effective — it makes GPU-based attacks expensive because GPUs have limited per-core memory.
scrypt, designed by Colin Percival, similarly uses memory-hardness to resist GPU/ASIC attacks. It's widely available and a solid choice if Argon2 isn't available in your stack.
Decision Guide: Which to Use?
| Use case | Recommended algorithm |
|---|---|
| Password storage (new systems) | Argon2id |
| Password storage (existing systems) | bcrypt (cost ≥ 12) |
| File integrity / checksums | SHA-256 or SHA-512 |
| Digital signatures | SHA-256 (with RSA or ECDSA) |
| HMAC / API authentication | HMAC-SHA256 |
| Blockchain / proof of work | SHA-256, SHA-3 |
| Data deduplication | SHA-256, SHA-512 |
Always Salt Your Hashes
A salt is a random value unique to each user that is concatenated with the password before hashing. Salts prevent precomputed rainbow table attacks — an attacker must hash each candidate password individually for each user. bcrypt, Argon2, and scrypt all generate and store salts automatically. If you're using SHA-256 for non-password purposes, add a salt manually.
Practical Implementation Notes
// Node.js — bcrypt
import bcrypt from "bcryptjs";
// Hash a password (cost factor 12)
const hash = await bcrypt.hash(password, 12);
// Verify
const match = await bcrypt.compare(submittedPassword, storedHash);
Use the Copynix Bcrypt tool to generate and verify bcrypt hashes directly in your browser for testing and debugging — no data is sent to any server.
Summary
SHA-256 is a fantastic general-purpose hash function — for everything except passwords. Its speed, which is a feature for checksums, becomes a vulnerability when an attacker can iterate billions of guesses per second. Use bcrypt (cost ≥ 12) for existing systems and Argon2id for new ones. The goal is to make password cracking economically impractical even after a database breach.
