Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Which hash function should you use?

On this page
  1. Step 1: are you hashing for security?
  2. Non-security: pick by performance and ergonomics
  3. Security: pick by what’s at stake
    1. General-purpose security hashing
    2. Password storage — completely different problem
    3. Message authentication — use HMAC, not plain hash
  4. The decision tree, condensed
  5. Common mistakes I keep seeing
  6. Try the tools

The naive answer is “SHA-256.” It’s right about 70% of the time. The other 30% it’s catastrophically wrong — either far overkill, or dangerously underkill, depending on what you’re hashing and why.

This is the practical decision tree, with the why behind each recommendation.

Step 1: are you hashing for security?

Two very different categories. The mistake of treating them as one breaks production roughly weekly.

Non-security hashing: ETags, deduplication, content addressing, Bloom filters, fingerprints, cache keys, sharding. The goal is “two identical inputs produce identical hashes; two different inputs almost always produce different hashes.” An attacker is not in your threat model.

Security hashing: signatures, integrity verification against a malicious actor, password storage, message authentication. An attacker is actively trying to find collisions or recover the input.

If you’re not sure: when an attacker finds two inputs with the same hash, does that hurt you? If yes, you’re in security mode.

Non-security: pick by performance and ergonomics

For non-security work, MD5 is genuinely fine. So is SHA-1. So is the xxhash family. Pick by speed and convenience, not strength.

Use caseBest pickWhy
ETag / cache keyMD5 or xxhashFast, fixed-width, low collision rate on real data
Deduplication of filesMD5 or SHA-1Existing tools (rsync, git) speak these
Content addressing in a trusted networkSHA-256Future-proof against scope expansion
Bloom filter / sharding hashxxhash or murmurDesigned for speed, not crypto
Database row hash for change detectionxxhash or MD5Speed matters when scanning rows

The case for “always SHA-256 even non-security” is conservatism: if your code’s threat model expands later, you don’t have to migrate. That’s a fine choice. Just don’t think faster algorithms are wrong — they’re optimised for the actual non-security workload.

Security: pick by what’s at stake

General-purpose security hashing

SHA-256 is the right default. Used by HTTPS certificates, JWT HS256/RS256, Git’s SHA-256 transition, Bitcoin, most modern code signing. Strong against all known attacks. Broadly supported.

Reach for SHA-512 when:

Reach for BLAKE3 when:

Avoid for new security work:

Password storage — completely different problem

Do not use any of the above for passwords.

The reason: SHA-256 is fast — that’s what makes it good for verification. But for password storage, fast is bad. An attacker who exfiltrates your hash database can try billions of passwords per second on a GPU, breaking weak passwords in hours.

Password-hashing functions are intentionally slow and have built-in salt:

FunctionStandardNotes
Argon2idOWASP 2023 defaultBest choice for new code; memory-hard, side-channel resistant
scryptRFC 7914Strong; widely supported, simpler tuning
bcrypt1999, still solidBattle-tested; ubiquitous library support

Pick argon2id if your platform has it. Bcrypt if it doesn’t and you need a sure thing. Never PBKDF2-SHA256 unless you’re pinned to a compliance regime that requires it; it’s the weakest of the modern options.

Message authentication — use HMAC, not plain hash

If you’re proving “this message wasn’t tampered with and was sent by someone with the secret,” use HMAC-SHA256, not plain SHA-256.

Why? Plain hash + secret has length-extension vulnerabilities for some hash families (SHA-256 is in fact mostly safe here, but the pattern is bad practice). HMAC is the right primitive for keyed authentication.

const key = await crypto.subtle.importKey(
  "raw", secretBytes,
  { name: "HMAC", hash: "SHA-256" },
  false, ["sign", "verify"]
);
const sig = await crypto.subtle.sign("HMAC", key, messageBytes);

This is the same operation that JWT HS256 uses under the hood. See jwt.tooljo.com for the JWT-specific UI.

The decision tree, condensed

Are you hashing a password or password-like secret?
├── YES → Argon2id (preferred), scrypt, or bcrypt. Stop reading.
└── NO ↓

Is an attacker in your threat model?
├── NO → MD5 / SHA-1 / xxhash all fine. Pick by speed.
└── YES ↓

Does the hash need to authenticate (prove a sender)?
├── YES → HMAC-SHA256. Don't use plain hash + secret.
└── NO ↓

Default to SHA-256.

Reach for SHA-512 if 64-bit perf matters.
Reach for BLAKE3 if extreme throughput matters.

Common mistakes I keep seeing

  1. “We use SHA-256(password)” — vulnerable to GPU brute force. Use a password-hashing function.
  2. “We use MD5 to verify the upload was complete” — fine, but call it a checksum, not security. This avoids the next dev assuming it’s tamper-proof.
  3. “We use SHA-1 because Git does” — Git is migrating away. Don’t tie new code to the laggard.
  4. “We use SHA-256(salt + password)” instead of bcrypt — still vulnerable to GPU brute force. The salt prevents rainbow tables but not parallel guessing. The slowness of bcrypt/argon2 is doing the work.
  5. “We use HMAC-MD5” — actually still considered safe (HMAC’s construction blunts the underlying hash’s weaknesses), but you should still upgrade to HMAC-SHA256 for forward-compatibility.

Try the tools

The math behind hashing is fascinating. The decision of which hash to use is mostly about correctly classifying the threat model. Get that right and the choice is usually obvious.