Which hash to use: MD5, SHA-256, HMAC or none

Checksums, file integrity, signatures, webhooks and passwords need different hash functions. Worked examples, real values and the mistakes behind breaches.

On this page

"Hash it" is advice that hides several different jobs. Detecting a corrupted download, naming a file by its content, proving a webhook came from your payment provider and storing a password all use hash functions, but they need different properties, and choosing the wrong one is behind real security failures. This article goes through each job with a concrete example and the values you can reproduce in the hash generator.

What a hash function promises

A cryptographic hash function turns any input into a fixed-size value (32 bytes for SHA-256) with three properties:

  1. Preimage resistance. Given a hash, you cannot compute an input that produces it.
  2. Second-preimage resistance. Given an input, you cannot find a different input with the same hash.
  3. Collision resistance. You cannot find any two inputs with the same hash.

It does not promise secrecy for short inputs. Anyone can hash every common password and compare, so "you cannot reverse a hash" is only true when the input is unpredictable.

The same text always gives the same hash, and a one-character change gives a completely different one. The sentence The quick brown fox jumps over the lazy dog has SHA-256 d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592; end it with cog instead and the hash is unrecognisably different: e4c4d8f3bf76b692de791a173e05321150f7a345b46484fe427f6acc7ecc81be.

Job 1: catching accidental corruption

A download, a backup or a file copied over a flaky network can lose or flip bits. Any checksum catches that: CRC-32 (used inside ZIP, PNG and gzip), MD5 or SHA-256. CRC-32 is the fastest and is designed exactly for random errors; its standard check value, the CRC of 123456789, is CBF43926.

The catch is the word "accidental". CRC-32 is linear, so anyone can change a file and adjust four bytes to keep the same CRC. If the file comes from somewhere an attacker could touch, you need the next job.

Job 2: integrity against an attacker

When you compare a downloaded installer against the SHA-256 published on the project's website, you rely on collision and second-preimage resistance. Here the algorithm matters:

  • MD5 has had practical collisions since 2004, and they can now be produced on an ordinary computer. Two different files with the same MD5 are easy to make.
  • SHA-1 fell in 2017, when researchers published two different PDF files with the same SHA-1.
  • SHA-256, SHA-384, SHA-512 and SHA-3 have no known practical attacks. SHA-256 is the usual choice; SHA-512 is equally strong and often faster on 64-bit processors.

The file hash checker compares a published value for you. Remember that a checksum only helps if you get it from a different, trusted place than the file itself.

Job 3: proving who sent a message (HMAC)

A webhook arrives saying "payment received". Anyone on the internet can send that request, so the provider signs it. A hash alone cannot do this: if the signature were SHA-256(message), an attacker could compute it too. You need a secret key, and the correct construction is HMAC:

HMAC(key, message) = H((key ⊕ opad) || H((key ⊕ ipad) || message))

Why not simply SHA-256(key || message)? Because SHA-256 (like MD5 and SHA-1) is vulnerable to length extension: from the hash of key || message, an attacker who does not know the key can compute the hash of key || message || padding || extra, and so forge a longer message. HMAC's two nested hashes close that hole. (SHA-3 is not vulnerable to length extension, but HMAC is still the standard way to use a key.)

Example values: with the key key and the fox sentence, HMAC-SHA256 is f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8. In your server code, compare the received signature with a constant-time comparison function, not ==, so timing does not reveal how many characters matched.

Job 4: passwords (use none of the above)

Fast hashes are exactly wrong for passwords. A modern graphics card computes billions of MD5 or SHA-256 hashes per second, so an attacker who steals a table of SHA-256 password hashes can try every common password and every short combination quickly, and identical passwords produce identical hashes across users.

Password storage needs a function that is deliberately slow and memory-hungry, with a random salt per user: Argon2id (the current recommendation of the OWASP Password Storage Cheat Sheet), scrypt or bcrypt. Your web framework almost certainly has one built in. Long random passwords from a password generator help too, but the server-side choice matters more.

Choosing quickly

JobUseAvoid
Detect accidental corruptionCRC-32, SHA-256
Verify a download or deduplicate untrusted filesSHA-256 or SHA-512MD5, SHA-1, CRC-32
Sign a webhook or API requestHMAC-SHA256SHA-256(key + message)
Store passwordsArgon2id, scrypt, bcryptAny plain or salted fast hash
Ethereum addresses and signaturesKeccak-256SHA3-256 (different padding)

Common mistakes

  • Hashing text with a hidden newline. echo 'abc' | sha256sum hashes four bytes. Use printf '%s' or echo -n to match the tool.
  • Mixing encodings. The same word in UTF-8 and UTF-16 gives different hashes. Agree on UTF-8.
  • Comparing hex case-sensitively. BA78… and ba78… are the same bytes. The tool's compare box ignores case and spaces.
  • Confusing Base64 and hex. A SHA-256 is 64 hex characters or 44 Base64 characters; both are the same 32 bytes.
  • Treating a hash as encryption. A hash cannot be decrypted; if you need the data back, use encryption instead.

Try it

Paste text into the hash generator to see every algorithm side by side, switch on HMAC with a test key to reproduce a webhook signature, and paste a published checksum into the compare box to see which algorithm it came from.

Tools mentioned in this article

Back to the blog