Hashing Algorithms Explained

"Just hash the password with SHA-256" is one of the most common mistakes in application security — not because SHA-256 is weak, but because it's fast, and fast is the wrong property for a password hash. Here's the distinction that actually matters, and what's currently recommended instead.

reference guide security OWASP-sourced

// the split that matters more than the algorithm name

Every hash function you'll meet falls into one of two categories with opposite design goals. Fast/general-purpose hashes (MD5, SHA-1, SHA-256, SHA-512) are built to process gigabytes per second — perfect for checksums and integrity checks, and exactly wrong for passwords, because an attacker with a stolen hash database can also compute billions of guesses per second. Password KDFs (bcrypt, scrypt, Argon2) are deliberately, tunably slow — that's the entire feature. The same property that makes SHA-256 great at hashing a 2GB file makes it bad at protecting a password.

inputSHA-256 · ~1 µshash≈ billions of guesses/secinputArgon2id · ~100 mshash≈ tens of guesses/sec
The same input through two different kinds of hash. SHA-256 finishes in microseconds — great for checksums, but it lets an attacker who steals a hash database try billions of password guesses per second. Argon2id is deliberately slow and memory-hungry, cutting that to a manageable handful of guesses per second.

// fast hashes: file integrity, checksums, content addressing

which one
SHA-256 recommendedthe right default — file integrity, signing, content-addressable IDs, Git's newer SHA-256 mode
SHA-512 strongestsame security family as SHA-256, 128-char hex output, faster than SHA-256 on 64-bit hardware for large data
SHA-1 deprecatedbroken for security since 2017; still seen in Git commit IDs and legacy systems as a non-security checksum
MD5 brokencryptographically broken since 2004; fine only for detecting accidental corruption, never for anything adversarial

// password hashing needs a KDF, not a hash

A Key Derivation Function adds two things a plain hash doesn't have: deliberate slowness (a tunable work factor) and, for the modern ones, deliberate memory cost — which makes cracking expensive even on specialized hardware (GPUs, ASICs) that laugh at plain SHA-256. Current OWASP Password Storage Cheat Sheet guidance, in order of preference:

current OWASP-recommended parameters
Argon2id preferredmin. 19 MiB memory, iteration count 2, parallelism 1 — memory-hard, resists GPU/ASIC cracking best
scryptif Argon2id unavailable — CPU/memory cost 2¹⁷, block size 8, parallelization 1
bcryptlegacy systems — work factor 10+, note the hard 72-byte password input limit
PBKDF2only when FIPS-140 compliance is required — 600,000+ iterations with HMAC-SHA-256
These numbers are a moving floor, not a ceiling — hardware gets faster every year, so OWASP revises them upward over time. Whatever you pick, use a library's default/adaptive settings rather than hand-picking a work factor once and never revisiting it.

// salting — why the same password shouldn't produce the same hash

A salt is random data mixed into the input before hashing, unique per record and stored alongside the hash (it isn't secret). Without one, two users with the password hunter2 get identical hash values, and an attacker can precompute a rainbow table — a lookup of hash → plaintext for common passwords — once, then crack every unsalted database instantly on lookup. Every modern password KDF (bcrypt, scrypt, Argon2) generates and manages a salt automatically as part of its output format; you don't add one manually.

no saltuser A: hunter2user B: hunter2same hashunique saltuser A: hunter2 + saltAuser B: hunter2 + saltBhash Ahash B
Hashing the same password without a salt produces the same hash for every user who picked it — one entry in a precomputed rainbow table cracks all of them at once. A random per-user salt mixed in before hashing makes identical passwords produce different hashes, so a precomputed table no longer applies.

// HMAC — a different tool, often confused with plain hashing

HMAC (Hash-based Message Authentication Code) combines a hash function with a secret key to prove both integrity and authenticity — that a message wasn't altered and that it came from someone who holds the key. A plain SHA-256 hash proves integrity only: anyone can compute a SHA-256 of anything, so a bare hash alongside a message proves nothing about who sent it. This is why webhook signatures, JWT's HS256 algorithm, and API request signing use HMAC-SHA256, not raw SHA-256 — the key is what turns "a hash" into "a hash only the real sender could have produced."

// what "broken" actually means — real collision attacks

collision resistance, defined
Preimage resistancegiven a hash, you can't find any input that produces it
Second-preimage resistancegiven input A, you can't find a different input B with the same hash
Collision resistanceyou can't find any two inputs that hash to the same value — the property MD5 and SHA-1 lost
MD5 — broken in 2004: Xiaoyun Wang's team published practical collisions, later refined into fast, deliberate collision generation. SHA-1 — broken in practice on February 23, 2017, when Google and CWI Amsterdam published "SHAttered," the first real SHA-1 collision, produced for roughly $110,000 of cloud compute. Both algorithms still run instantly and produce a hash for any input — they're "broken" in the specific sense that an attacker can now deliberately construct two different inputs sharing one hash, which defeats any use case relying on that not being possible (signatures, integrity proofs). Neither is safe for new security-relevant work.

// checksums vs cryptographic hashes — not the same category

CRC32 and similar checksums are designed to catch accidental corruption (a flipped bit from a bad network or disk) cheaply — they are not designed to resist someone deliberately engineering a collision, and take microseconds even on huge inputs. Cryptographic hashes (the SHA family) are slower but resist deliberate tampering. Using CRC32 where you need tamper-resistance is a common quiet mistake — it looks the same as a real hash in a hex string, but offers none of the security guarantee.

// try it yourself

Compute MD5, SHA-1, SHA-256, and SHA-512 of any text with the Hash Generator — computed locally via the browser's Web Crypto API, nothing sent to any server.