Quick Decision Table
| Use case | Use this | Don't use |
|---|---|---|
| Detecting accidental file corruption | MD5 / CRC32 | — |
| Cache key / dedupe key for trusted content | MD5 / xxHash | — |
| Verifying download from untrusted mirror | SHA-256 | MD5, SHA-1 |
| Digital signatures, certificates, JWT | SHA-256 / SHA-384 | MD5, SHA-1 |
| Git-style content addressing | SHA-256 (or SHA-1 for legacy) | MD5 |
| Storing user passwords | Argon2id / bcrypt / scrypt | MD5, SHA-*, plain hashes |
| HMAC for API request signing | HMAC-SHA-256 | HMAC-MD5 (legacy only) |
The 4 Hash Use Cases
Cryptographic hash functions are deceptively single-purpose-looking. In reality they serve four distinct jobs, each with different requirements:
MD5 — Fast But Broken
MD5 was published in 1992 and produces a 128-bit (16-byte) digest. It is one of the fastest cryptographic hashes ever designed and is still widely used for non-security-critical jobs: ETags, dedupe keys, file fingerprints in build systems, cache invalidation.
MD5 is cryptographically dead. The first published collision attack came in 2004 (Wang et al.); by 2008 researchers were forging trusted SSL certificates with chosen-prefix collisions in under 24 hours on modest hardware. Today, generating MD5 collisions on demand takes seconds on a laptop.
Practical rule: MD5 is fine when both inputs are produced by a trusted party — your own build system, your own dedupe table. MD5 is a security failure when one of the inputs comes from somebody who might want to fool you.
SHA-1 — Deprecated
SHA-1 produces a 160-bit (20-byte) digest. It powered HTTPS for a decade, signed every TLS certificate until 2017, and is still the foundation of Git's content-addressed storage.
In February 2017, Google and CWI Amsterdam announced the "SHAttered" attack: two distinct PDFs with identical SHA-1 digests, produced for roughly $110,000 of cloud GPU time. By 2020, the attack cost had dropped to ~$45,000. Chosen-prefix collisions — strictly stronger than identical-prefix — were demonstrated in 2019.
Browsers stopped trusting SHA-1 certificates in 2017. Git is migrating to SHA-256 (Git 2.29+ supports it; the migration is slow because of ecosystem inertia). New code in 2026 should not use SHA-1 for any security-relevant purpose.
SHA-256 — The Modern Default
SHA-256 is part of the SHA-2 family (Secure Hash Algorithm 2), published by NIST in 2001. It produces a 256-bit (32-byte) digest. After a quarter-century of public scrutiny, no practical collision or pre-image attack has been demonstrated. The best generic attack on SHA-256 is brute force at 2^128 operations for collisions — vastly beyond any plausible adversary, including a state-funded one.
SHA-256 is the foundation of TLS 1.3 certificates, JWT (HS256/RS256/ES256), Bitcoin's proof of work and address derivation, Git's next-generation object IDs, every modern code-signing system, and most cloud-provider request signing (AWS Signature v4, GCP HMAC).
When in doubt, use SHA-256. It is the boring, correct answer for almost every "I need a cryptographic hash" question.
SHA-512 — Bigger ≠ Always Better
SHA-512 is also part of SHA-2 and produces a 512-bit (64-byte) digest. Surprisingly, on 64-bit CPUs it is often faster than SHA-256 — its internal operations are 64-bit, which maps to one register per word, while SHA-256 wastes the upper half.
When does the larger digest actually help? Three cases:
- Future-proofing against quantum computers — Grover's algorithm halves the effective security of any hash, so SHA-512 still gives 256 bits of post-quantum collision resistance versus SHA-256's 128 bits.
- HMAC keys longer than 64 bytes — HMAC-SHA-512 has a 128-byte block size, so longer keys aren't pre-hashed.
- Truncated digests — taking the leading 256 bits of SHA-512 (sometimes called SHA-512/256) is faster on 64-bit CPUs than computing SHA-256 directly and gives the same 128-bit collision resistance.
For everyday hashing where digest length isn't a constraint, both work. If you're writing a new spec in 2026 and want to be conservative against quantum, SHA-384 or SHA-512 is the slightly-paranoid default.
Why Cryptographic Hashes Are NEVER for Password Storage
A modern GPU can compute roughly 100 billion SHA-256 hashes per second. For a typical 8-character ASCII password (about 53 bits of entropy), the entire keyspace is exhausted in under 30 hours on a single rented A100. With 8× A100s, under 4 hours.
Password hashing functions — bcrypt (1999), scrypt (2009), Argon2 (2015, winner of the Password Hashing Competition) — deliberately make this expensive in three ways: a tunable iteration count, mandatory per-user salt, and (for scrypt/Argon2) memory hardness so that GPU and ASIC attacks lose their advantage.
Use Argon2id with parameters tuned to ~250ms on your server (a starting point: m=64MB, t=3, p=1). Bcrypt with cost 12+ is still acceptable. Scrypt remains fine. Anything based on raw SHA-* — even with a salt and millions of iterations (PBKDF2) — is the weakest of the modern options because it isn't memory-hard.
Hard rule: If your code does anything like sha256(password + salt), you have a vulnerability. Use a password hashing library — never roll your own.
Web Crypto API in Practice
Modern browsers and Node.js 20+ ship the same standard crypto.subtle API. No external library needed:
async function sha256(text) {
const data = new TextEncoder().encode(text);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
await sha256('hello world');
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
// Streaming for large files (Node.js)
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
async function fileHash(path, algo = 'sha256') {
const hash = createHash(algo);
for await (const chunk of createReadStream(path)) {
hash.update(chunk);
}
return hash.digest('hex');
}
// HMAC for signed cookies / API requests
async function hmacSha256(key, message) {
const cryptoKey = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(key),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const sig = await crypto.subtle.sign(
'HMAC',
cryptoKey,
new TextEncoder().encode(message)
);
return Array.from(new Uint8Array(sig))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}The Web Crypto API supports SHA-1, SHA-256, SHA-384, and SHA-512. Notably absent: MD5. The browser vendors made the deliberate choice not to expose it, since most legitimate uses can be served by SHA-256 and the tiny minority that genuinely need MD5 (legacy protocols, ETags) can ship a 5-line pure-JS implementation.
Hash any text instantly
MD5, SHA-1, SHA-256, SHA-512 — runs entirely in your browser, no upload.