Hashing, MACs, and Integrity
A cryptographic hash turns any input into a fixed-length fingerprint that is infeasible to reverse or to collide — the tool for integrity, not secrecy. But a bare hash proves nothing about who produced it. A MAC — a message authentication code such as HMAC — adds a secret key, so the recipient knows the data is both unaltered and from someone who holds the key.
This topic separates three jobs people constantly conflate: hashing for integrity, MACs for authenticated integrity, and — in the next topic — hashing for password storage, which has entirely different rules. Getting the wrong one is a recurring source of real vulnerabilities.
What a Cryptographic Hash Guarantees
A cryptographic hash is deterministic, fixed-length, one-way, and collision-resistant: the same input always gives the same digest, you cannot feasibly work backward to the input, and you cannot feasibly find two inputs with the same digest. SHA-256 is the workhorse. MD5 and SHA-1 are broken for security use — practical collisions exist — so they survive only as non-security checksums against accidental corruption.
Integrity Checking in the Wild
Publishing a file's hash lets a downloader verify it was not corrupted or swapped — but only if the hash itself arrives over a channel the attacker cannot touch. A hash posted on the same page as the download helps against accidental corruption, not against an attacker who simply changes both the file and the hash. Integrity is only as trustworthy as the delivery of the value you check against, which is where signatures come in later.
HMAC — Integrity Plus Authenticity
Combine a hash with a secret key and you get a tag that only key-holders can compute or verify. HMAC proves a message is both unaltered and from the right party, and it is the mechanism behind signed API requests and tamper-proof cookies. Critically, use HMAC rather than naively hashing "message plus secret," because that naive construction is vulnerable to length-extension attacks that HMAC is specifically built to resist.
# the client and api.meridian.example share a secret key
BODY='{"amount":500,"to":"acct-91"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SHARED_SECRET" -hex | awk '{print $2}')
# the client sends BODY plus the signature header:
# X-Signature: <SIG>
# the server recomputes the HMAC over the received body and compares in CONSTANT TIME
The client signs the request body with the shared secret; the server recomputes the same HMAC over what it received and compares. If a single byte of the body was tampered with in transit, the recomputed tag will not match and the request is rejected. The one detail that voids the whole scheme is the comparison: it must be constant-time.
Constant-Time Comparison
Verifying a MAC with an ordinary string comparison leaks timing — a comparison that bails out on the first mismatched byte tells an attacker, through response timing, how much of their forged tag was correct, letting them recover it byte by byte. Secrets and tags must be compared with a constant-time function the library provides. It is a small detail that completely undoes the MAC if missed.
Hash — integrity only; anyone can compute it, so it proves nothing about origin.
MAC / HMAC — integrity plus authenticity with a shared secret; both sides hold the same key, so either could have produced the tag.
Digital signature — integrity, authenticity, and non-repudiation with a key pair; only the private-key holder can sign, anyone can verify (next topics).
- Using MD5 or SHA-1 for anything security-relevant — both have practical collisions and survive only as non-security checksums.
- Publishing a plain hash over the same untrusted channel as the file and calling it integrity — an attacker swaps both; the hash needs a trusted path or a signature.
- Hand-rolling keyed hashing as hash(secret + message), which is vulnerable to length-extension; use HMAC, which resists it by construction.
- Comparing MACs or tokens with a normal equality check, leaking the correct value byte by byte through timing.
- Confusing a hash (integrity) with a MAC (integrity plus origin) and shipping one where the other was needed.
- Use SHA-256 or the SHA-3 family for integrity, and HMAC when you also need to prove origin with a shared secret.
- Deliver integrity values over a trusted channel or bind them with a signature — a hash is only as trustworthy as its delivery.
- Always use HMAC rather than a hand-built keyed hash, to sidestep length-extension and construction mistakes.
- Compare secrets and tags in constant time using the library's dedicated function.
- Match the primitive to the job: hash for integrity, HMAC for keyed integrity, a signature when verifiers should not also hold signing power.
Knowledge Check
What does an HMAC prove that a bare hash does not?
- That it is unaltered and came from a holder of the shared key
- That the message contents are hidden from anyone in between
- That the message can be decrypted back to its original form
- That the sender cannot deny having sent it (non-repudiation)
Why must a MAC be verified with a constant-time comparison?
- An early-exit compare leaks, via timing, a forged tag's correct prefix
- A constant-time comparison is meaningfully faster than a normal one
- Normal comparison would decrypt the secret key by accident
- It is required only when the underlying hash happens to be MD5
A vendor posts a download and its SHA-256 hash on the same web page. What does that hash actually protect against?
- Accidental corruption, but not an attacker who edits both
- Any attacker, because SHA-256 cannot be forged
- Nothing at all, since hashes provide no integrity
- Eavesdropping, by keeping the download confidential
You got correct