Choosing a hash: a decision guide

One table mapping the job — integrity, indexing, authentication, password storage, signatures — to a concrete algorithm and parameters, plus the questions that change the answer.

Match the job to the algorithm

JobUseParametersNever use
File or payload integritySHA-256 (or BLAKE3 for speed)256-bit digestMD5, SHA-1, CRC32
Detect accidental corruptionCRC32 or xxHashAny widthAny of these for security
Hash table key distributionSipHash or xxHash, seeded64-bit, random seedUnseeded FNV against untrusted input
Message authenticationHMAC-SHA256128 to 256-bit tag, per-purpose keySHA256(key || msg)
Sessions and cookiesHMAC-SHA256 or Ed25519Include an expiry in the signed payloadA random id with no expiry
Password storageArgon2id64 MiB, 3 passes, tuned to about 100 msSHA-256, MD5, unsalted anything
Password fallbackbcrypt cost 12 or PBKDF2 600kPer-user saltA shared salt or no salt
Digital signatureEd25519, or ECDSA P-256, or RSA-PSSSHA-256 digestA signature over an unsigned hash
Cache keySHA-256 truncated128 bits is plentyEmbedding raw user input as the key
Sharding and partitioningxxHash or SHA-256 truncated64 to 128 bits, versioned schemeLanguage built-in hash across services
Content addressingSHA-256256-bit digestSHA-1 for new systems
Key derivationHKDF-SHA256 or BLAKE3 derive_keyDistinct context per purposeReusing the raw secret directly

Four questions that change the answer

  • Who can choose the input? If an attacker can, you need collision resistance and, for tables, a seeded hash.
  • Who holds the key? A shared secret means HMAC; a private key means a signature; nobody means a plain digest that proves only integrity.
  • How long must it stay secure? Signatures and content addresses outlive the code, so choose 256-bit digests and plan for post-quantum migration.
  • What is the throughput budget? Password hashing intentionally costs 100 milliseconds; a per-request cache key cannot. Match the cost to the frequency.
# one place for every hash decision, so the choice is reviewable
import hashlib, hmac, secrets

def integrity(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()

def cache_key(user: str, path: str) -> str:
    return hashlib.sha256(f"{user}|{path}".encode()).hexdigest()[:32]

def sign(payload: bytes, key: bytes) -> bytes:
    return hmac.new(key, payload, hashlib.sha256).digest()

def new_salt() -> bytes:
    return secrets.token_bytes(16)

The recurring mistakes

MistakeConsequenceCorrect approach
Using MD5 for integrityForgery is practicalSHA-256 or BLAKE3
Comparing digests with ==Timing leakA constant-time comparison
Reusing one key everywhereCompromise spreads across systemsDerive a key per purpose with HKDF
Truncating a hash to 32 bitsCollisions in ordinary useKeep at least 64 bits, 128 for uniqueness
Hashing a password without a slow KDFOffline cracking on a GPUArgon2id with tuned parameters
Signing a manifest but not its filesOne artifact can be swappedSign a hash list covering every artifact
Assuming a hash is portableKeys map differently across languagesDefine the algorithm and encoding explicitly
💡
Write the decision down next to the code. The most expensive hash problems are not cryptographic — they are six teams each choosing differently for the same problem, and nobody able to say later why MD5 was still in the payment path.

FAQ

If I only remember one rule, what should it be?
A hash for integrity is not a hash for authentication. The moment a secret or an attacker is involved, use HMAC, a KDF or a signature instead of a plain digest.
How do I choose between HMAC and a signature?
Use HMAC when both parties share a secret and either may verify. Use a signature when the verifier must not be able to produce valid signatures, as with public downloads or third-party APIs.

Non-cryptographic hashes: CRC32, FNV and xxHash HMAC and keyed hashing

Last refreshed 2026-09-18.