What is a hash function?
A deterministic shrinker that turns any blob of bytes into a fixed-size fingerprint — the same primitive that powers hash tables, Git commits, and password storage.
On this page
The picture version
Five pictures for a reader who has never run a checksum. The prose below fills in the seams the pictures skip.
1 · The problem
You trusted a stranger’s server because 64 characters matched.
2 · Property one
Anything in, always the same size out.
3 · Property two
Change one bit and the output is unrecognisable.
4 · The part that sounds like a flaw
Two different files must share an output somewhere. That’s fine.
5 · Keep this card
The whole thing on one index card.
Why it exists
You download a 4 GB Linux ISO from a mirror you’ve never heard of. Next to
the download link, the project’s own site prints a line of gibberish:
a1b2c3… — 64 hex characters labelled “SHA-256.” You run one command on the
file you just downloaded, and out comes the same 64 characters. You now
believe you got the real file, from a stranger’s server, without the project
ever seeing your copy. Note where the fingerprint came from: that argument
only holds because you read the hash off the project’s own site rather than
off the mirror. Whoever can swap the download can usually swap the hash
printed next to it.
That’s the trick worth explaining, and it’s the running example for the rest of this post: your 4 GB download and its 64-character fingerprint. For it to work, one function has to squeeze four billion bytes down to thirty-two, give everyone who runs it the same answer, and still be impossible to fool.
The same primitive shows up from a completely different direction, too. If you have a million keys and want lookup that doesn’t grow with the size of the collection, you can compute directly from the key which slot in an array its value lives in — no searching, just jumping. (See hash table for that story.) Both jobs want a small, deterministic, hard-to-collide fingerprint of arbitrary input. They split into two families — fast non-cryptographic hashes and slower cryptographic ones — tuned for different threat models but sharing a shape.
Why it matters now
You are using hash functions constantly, usually without noticing.
- The
dict/Map/HashMap/Setin your language’s stdlib — hash-backed in most implementations, even where the spec only promises fast lookup. - Every Git commit ID is a hash of the commit’s contents.
- Package managers verify downloads against a published hash.
- Password storage keeps a hash-derived verifier, never the password itself.
- Blockchains chain blocks together by hash.
- Content-addressed storage (IPFS, OCI/Docker image layer digests) names blobs by their hash so identical content collapses to one copy.
- HMAC wraps a hash function to authenticate messages inside TLS.
The short answer
hash function = deterministic map from arbitrary bytes → fixed-size fingerprint
Picture to keep: a meat grinder with a fixed-size mould at the end. Anything you put in — a steak, a whole cow, one pea — comes out as the same-sized patty, and the same input always produces the exact same patty. Unlike a real grinder, though, changing one atom of the input produces a completely different-looking patty, and no one can work backwards from the patty to the cow.
You feed in any number of bytes — one character, a 4 GB video, the entire Linux kernel source — and you get back a fixed-size output (say, 256 bits). Same input always produces the same output. Different inputs should produce different outputs, and for cryptographic hashes it should be infeasible to engineer a clash.
There are two flavors, optimized for opposite goals:
- Non-cryptographic (xxHash, MurmurHash, FNV) — as fast as possible while spreading inputs evenly. Used inside hash tables and bloom filters. Speed is the feature; security isn’t on the menu.
- Cryptographic (SHA-256, SHA-3, BLAKE3) — slower, with extra guarantees about collisions and one-wayness. Used wherever an adversary might try to forge a fingerprint.
Reach for the wrong one and you either ship a slow hash table or a forgeable signature.
How it works
The shape never changes — any input, fixed-size output:
input (any length) ─► [ hash function ] ─► output (fixed length)
"hi" ─► SHA-256 ─► 8f434346… (256 bits)
"hi!" ─► SHA-256 ─► c0ddd62c… (256 bits)
your 4 GB download ─► SHA-256 ─► <64 hex chars, same 256 bits>
(The first two are real — printf 'hi' | shasum -a 256 reproduces them. The
third depends on your file, so there’s no honest constant to print here.)
The interesting part is what that function has to survive. Build it up by trying the obvious thing and watching it break.
Attempt 1: just compare the file to a known-good copy. Correct, and useless — you’d have to download the good copy to compare against, which is the thing you were trying to avoid. What you want is a short stand-in for 4 GB of bytes.
Fix 1: add up all the bytes. Sum every byte of the ISO into a 256-bit counter and publish that. It’s short, it’s fast, and it’s deterministic — same file, same number, on any machine, any run, forever. Without determinism nothing else in this post means anything.
But sums collide on purpose. Add 1 to one byte, subtract 1 from another, and the total is unchanged — an attacker can edit your ISO freely as long as the edits cancel. Worse, similar files get similar sums, so the fingerprints cluster instead of spreading over the output space.
Fix 2: avalanche.
A good hash spreads inputs evenly across the whole output space; a
cryptographic one goes further, so flipping a single input bit flips about
half the output bits, unpredictably. That’s why "hi" and "hi!" above look
utterly unrelated. Now a one-byte tamper with the ISO produces a fingerprint
that shares nothing with the published one.
But “unrelated-looking” isn’t “unfindable.” A determined attacker doesn’t need to predict the output — they can search. Generate trillions of malicious ISO variants (padding bytes are free) and look for one whose fingerprint happens to match.
Fix 3: collision resistance — and its stricter cousin, second-preimage resistance. It should be infeasible to find any two inputs with the same output, and harder still to hit a specific published fingerprint. This is a strictly stronger demand than “spreads evenly,” and it’s where hash functions go to die: MD5 and SHA-1 both once claimed it and both now have published collisions, so neither belongs anywhere collision resistance matters. SHA-256 (FIPS 180-4) and SHA-3 (FIPS 202) are the standardized choices today; BLAKE3 is a modern unbroken alternative with no equivalent standards status.
One more failure, from a different direction. A website wants to check your password without storing it, so it stores something derived from the password by hashing. If the database leaks, can the attacker recover the passwords?
Fix 4: preimage resistance. Two separate things are going on here, and it’s worth keeping them apart. Non-invertibility is free: your 4 GB download does not fit inside 256 bits, so the mapping is many-to-one and no unique inverse exists. But many-to-one doesn’t stop an attacker from finding some input that hashes to the target — that extra guarantee is preimage resistance, and it’s a designed cryptographic property, not a consequence of squeezing.
It’s also not enough on its own for passwords. Human passwords come from a small enough space that an attacker doesn’t need to invert anything — they just hash guesses until one matches. That’s why real password storage uses a salt plus a deliberately slow password-hashing function rather than a plain fast SHA-256. The slowness comes in two strengths: PBKDF2 and bcrypt are iterative — they cost more time but little memory, so an attacker with many parallel cores still gets good value — while Argon2id and scrypt are memory-hard, forcing each guess to occupy a configurable amount of RAM and blunting the GPU and ASIC advantage. Prefer the memory-hard pair where you get the choice. See password hashing.
Not a feature: hash functions are not encryption. There is no key and no decrypt. If someone asks you to “decrypt this hash,” they’re confused about what a hash is.
The cleanest way to remember the split: every hash function is deterministic and tries to avalanche. Cryptographic hashes additionally promise collision and preimage resistance — and pay for it in speed, which is exactly why hash tables don’t use them.
You started with hash function = deterministic map from arbitrary bytes → fixed-size fingerprint. What did the ISO download add? — + the *hardness* guarantees are what you're actually buying. Determinism is the easy half; the
whole reason you trust a stranger’s mirror is the part of the definition that
says nobody can find a second file with your fingerprint.
Famous related terms
- Hash table —
hash table = array + hash function— the canonical non-cryptographic use; the hash picks a bucket. - HMAC —
HMAC ≈ keyed hash for message authentication— wraps a hash function with a secret key so only key-holders can produce valid tags. Used inside TLS, JWTs, AWS request signing. - Salt —
salt = unique random prefix per input— defeats rainbow-table precomputation; see password hashing. - Merkle tree —
Merkle tree = binary tree of hashes of hashes— lets you prove one leaf is in a huge dataset by showing only a logarithmic chain of hashes. Powers Git, Bitcoin, certificate transparency. - Bloom filter —
bloom filter = bit array + k hash functions— a probabilistic set-membership test that trades false positives for tiny memory. - Cryptographic vs non-cryptographic — same shape, different tunings: xxHash is fast and forgeable; SHA-256 is slower and (currently) not. Pick by threat model, not by familiarity.
Going deeper
- The Keccak / SHA-3 design rationale from the Keccak team — the primary source for “what is actually inside a modern cryptographic hash,” and how the sponge construction differs from the Merkle–Damgård lineage of MD5/SHA-1/SHA-2.
- Introduction to Algorithms (CLRS), the hash-table chapter — read this for the data-structure side of the family, and specifically for what guarantee “universal hashing” buys you that a fixed hash function can’t.
- Bruce Schneier, Applied Cryptography — the rabbit hole if you want the collision/preimage/second-preimage framing worked out properly; older editions predate SHA-3 and BLAKE3, but that framing hasn’t aged.
No throughput numbers here on purpose: they move with CPU generations and implementation quality, so a published figure goes stale faster than it becomes useful. Measure on your own hardware if the speed matters to you.