Frontend Labs
⋯

Part 3 — Hashing: fingerprints for data

Chapter 8 — What a hash function is

In one sentence

A hash function turns any input into a fixed-size fingerprint, such that the same input always gives the same fingerprint and you can't work backwards.

The idea

   ANY input, any size                     Fixed-size output
   ------------------                      -----------------
   "hello"                    -->  SHA-256  -->  2cf24dba5fb0a30e26e83b2ac5b9e29e
                                                 1b161e5c1fa7425e73043362938b9824
   a 4 GB video file          -->  SHA-256  -->  (a different 64 hex characters)
   the empty string           -->  SHA-256  -->  e3b0c44298fc1c149afbf4c8996fb924
                                                 27ae41e4649b934ca495991b7852b855

Always 256 bits (64 hex characters) out, regardless of input size.

The properties that make it useful

A cryptographic hash function must have all four:

   1. DETERMINISTIC        same input -> always the same output
                           (otherwise it's useless for comparison)

   2. ONE-WAY              given the hash, you cannot find the input
      (preimage resistance) except by guessing

   3. AVALANCHE            change ONE BIT of input -> ~half the output bits flip
                           (so similar inputs look completely unrelated)

   4. COLLISION RESISTANT  it's infeasible to find TWO inputs with the
                           same output

Seeing the avalanche effect

   sha256("hello")   = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
   sha256("hello ")  = 5e3235a8346e5a4585f8c58562f5052b8fe26a3bb122e1e96c76784964dfc461
                       ^^ one added space -- completely different output
   sha256("Hello")   = 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
                       ^^ one changed bit (case) -- completely different

This is why hashes detect tampering. Any change, however small, produces an unrecognisably different result.

Collisions must exist (and why that's OK)

Infinite possible inputs, finite outputs (2^256 for SHA-256). So collisions mathematically must exist. The security property is that finding one is infeasible.

The birthday paradox: because of how probability works, finding any collision takes about 2^(n/2) work, not 2^n. So SHA-256's 256-bit output gives 128 bits of collision resistance, not 256. This is why "128-bit security" is the target and why SHA-256 (not SHA-128) is the baseline.

What hashes are for

Use How
Integrity checking Publish the hash of a download; the user verifies
Deduplication Same hash = same content, cheaply
Data structures Hash tables, Merkle trees, git commits, blockchains
Password storage With critical modifications — see Chapter 10
Digital signatures Sign the hash, not the (huge) document (Chapter 21)
Commitment Publish a hash now, reveal the data later, proving you didn't change it

What hashes are NOT for

   NOT encryption      -- there's no key and no way back. You can't "decrypt a hash".
   NOT for passwords   -- not plain, anyway. Chapter 10.
   NOT for secrecy of  -- if the input space is small (a credit card number,
   low-entropy data       a phone number, a UK postcode), an attacker just
                          hashes every possibility and looks yours up.

That last one catches people. Hashing an email address does not anonymise it — there are only so many email addresses, and attackers have precomputed them.

Practice (20 min)

cd ~/sec-lab/scratch

# --- basic hashing ---
printf 'hello' | openssl dgst -sha256
printf 'hello' | shasum -a 256          # same thing, different tool

# --- avalanche: change one character ---
for s in "hello" "hellp" "Hello" "hello "; do
  printf "%-8s " "$s"; printf "%s" "$s" | openssl dgst -sha256 | awk '{print $2}'
done

# --- deterministic: run it 3 times ---
for i in 1 2 3; do printf 'hello' | openssl dgst -sha256 -r; done

# --- fixed size regardless of input ---
printf 'a' | openssl dgst -sha256 | awk '{print length($2), $2}'
head -c 10000000 /dev/urandom | openssl dgst -sha256 | awk '{print length($2), $2}'

# --- verifying a download (do this for real, always) ---
echo "important data" > file.txt
openssl dgst -sha256 file.txt > file.txt.sha256
cat file.txt.sha256
# now tamper with it:
echo "important data " > file.txt        # added ONE space
openssl dgst -sha256 file.txt            # compare -- completely different

Break it: hash a 4-digit PIN with SHA-256. Now write four lines of Python that recover the PIN from the hash. How long did it take? That's why Chapter 10 exists.

python3 - <<'PY'
import hashlib
target = hashlib.sha256(b"4271").hexdigest()
print("hash:", target)
for i in range(10000):
    if hashlib.sha256(f"{i:04d}".encode()).hexdigest() == target:
        print("cracked:", f"{i:04d}"); break
PY

Common confusions

  • "Can I decrypt this hash?" No. There is nothing to decrypt. If someone "reverses" a hash they either guessed the input or looked it up in a table of precomputed hashes.
  • "Hashing anonymises personal data." Only if the input has high entropy. Hashed emails, phone numbers, and IDs are readily reversible by brute force and are generally still personal data under GDPR.
  • "Two files with the same hash are the same file." For a strong hash, practically yes. For MD5 or SHA-1, no — see Chapter 9.

Check yourself

  1. Name the four properties of a cryptographic hash function.
  2. Why must collisions exist?
  3. Why does a 256-bit hash give 128-bit collision resistance?
  4. Why doesn't hashing protect a credit card number?

Further reading

  • Video: Computerphile, "Hashing Algorithms and Security" (~8 min).
  • Book: Serious Cryptography, Chapter 6 (Hash Functions).
  • Tool: crackstation.net — paste a hash of a common word and watch it come back instantly. A visceral lesson in why unsalted hashes fail.

Generated from real-life-guide.md · 8 Oct 2026 16:36