Hashing: Which Algorithm for Which Job

Checksums, digital signatures and password storage need three different families. Using one for another is how breaches happen.

A hash function turns input of any size into a fixed-size digest. The same input always gives the same output, and a tiny change gives a completely different one. Beyond that, hash functions differ enormously, and the differences are the entire subject.

Three jobs, three families

JobUseNever use
Detecting accidental corruptionCRC32, xxHash, MD5
Integrity against an attackerSHA-256, SHA-512, BLAKE3MD5, SHA-1
Storing passwordsArgon2id, scrypt, bcryptAny of the above

The third row is the one that causes breaches, and the reason is counter-intuitive: general-purpose hashes are disqualified for passwords because they are fast.

Why speed is the wrong property for a password

A commodity GPU computes billions of SHA-256 hashes per second. If your database leaks and passwords were stored as SHA-256, an attacker simply hashes candidate passwords until the digests match. Common passwords fall instantly; an eight-character password from a realistic character set falls in hours.

Password hashes are therefore designed to be slow and, crucially, memory-hard — Argon2id and scrypt require a configurable amount of RAM per computation, which is what defeats GPUs, since a GPU's advantage is thousands of cores sharing limited memory. Tuning the cost so a single hash takes ~100ms on your hardware makes an offline attack millions of times more expensive while costing your login endpoint nothing noticeable.

Salting — a unique random value per password, stored alongside — is separate and also required. It stops one precomputed table from attacking every account at once. Modern password hashes handle salting for you, which is another reason not to assemble your own.

MD5 and SHA-1

Both are cryptographically broken: chosen-prefix collisions are practical, meaning an attacker can construct two different inputs with the same digest. That is fatal for signatures and certificates.

It is not fatal for checking whether a file downloaded correctly. Disk corruption does not craft collisions. So an MD5 published next to a download is fine against accident and worthless against a compromised mirror — which is exactly why projects publish SHA-256 alongside a signature.

Comparing digests safely

Comparing two digests with == leaks timing information: a naive comparison returns as soon as bytes differ, and an attacker measuring response times can recover a secret byte by byte. Any comparison of a secret digest — an HMAC, a token — should use a constant-time function (hash_equals, crypto.timingSafeEqual, hmac.compare_digest).

HMAC, when the hash needs a key

A plain hash proves nothing about who produced it — anyone can hash a modified message and attach a new digest. HMAC combines a secret key with the message so only key-holders can produce a valid tag. This is what signs webhooks and API requests, and it is why appending a secret to a message and hashing that is not equivalent: naive constructions are vulnerable to length-extension, which HMAC's nested structure is specifically designed to prevent.

Quick reference

Hash a string → · File checksums → · Back to all articles