Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why → how

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.

Computer Science intro Apr 30, 2026 · updated Aug 25, 2026 · 9 min read

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.

a 4 GB file from a mirror you’ve never heard of one command a1b2c3… (64 characters) matches what the project published You now believe you have the real file — without trusting the server at all. Four gigabytes of trust, resting on 64 characters. that only works because of some very specific properties, and they are worth knowing
The mirror could have tampered with anything, and you check it with a string shorter than this sentence. That download is the running example, and the rest of the post is what has to be true for the check to mean something.

2 · Property one

Anything in, always the same size out.

one charactera 4 GB videoan entire library all exactly the same length And the same input always produces the same output, on any machine, forever. which is what lets two people compare results without comparing files
Whatever you feed it — one byte or a library — the result is a fixed-size string, and the same input always gives the same result anywhere. That fixed size is what makes the check cheap to publish and cheap to compare.

3 · Property two

Change one bit and the output is unrecognisable.

hello world hello worle one character apart b94d27b9934d3e08… 4f2a91c05e7d1b63… nothing in common There is no “nearly the same” result for a nearly-identical file. so a tampered download doesn’t produce a nearly-matching string — it produces an obviously wrong one
A one-character edit rewrites the whole output, with no partial resemblance to the original. That is what makes comparison a yes-or-no test, rather than something you have to eyeball for closeness.

4 · The part that sounds like a flaw

Two different files must share an output somewhere. That’s fine.

every file that could ever exist no limit a fixed number of possible outputs So collisions exist. They have to — you cannot fit the unlimited into the finite. The guarantee was never “no two files match.” It is that nobody can find a pair on purpose.
There are unlimited possible inputs and only a fixed number of outputs, so different files must sometimes share one. What a good hash promises is not that collisions don’t exist but that nobody can construct one — which is why a hash can be broken without the arithmetic changing at all.

5 · Keep this card

The whole thing on one index card.

a hash function = any input → a fixed-size fingerprint + the same input always gives the same one + and no way to work backwards Which is how 64 characters can vouch for four gigabytes. the last line is the one that separates a checksum from a security guarantee
Picture to keep: a meat grinder with a fixed-size mould at the end — a steak, a whole cow or one pea all come out as the same-sized patty, and the same input always gives the identical patty. Unlike a real grinder, changing one atom of the input produces a completely different-looking patty, and nobody can work backwards from the patty to the cow.

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 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:

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.

Going deeper

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.