🧬 07-06: Hashing and Message Integrity¶
📌 What Is a Cryptographic Hash Function?¶
A cryptographic hash function takes an input of any size — a single character, a whole movie file, an entire database — and produces a fixed-size output called a hash (or digest).
hash_function("hello") -> 2cf24dba5fb0a30e26e83b2ac5b9e29e...
hash_function(entire_contents_of_a_movie) -> 9e107d9d372bb6826bd81d3542a419d6...
Both outputs above are the same length, no matter how different the inputs were in size. This is the whole point: a hash acts like a compact, unique-looking fingerprint for a piece of data.
💡 Hashing is not encryption. There's no key, and it's not reversible — you cannot "decrypt" a hash back into the original data. A hash only ever answers one question: "does this data match what I expected?"
🔑 The Properties That Make a Hash "Cryptographic"¶
Not every hash function is suitable for security. A cryptographic hash function must have these properties:
| Property | Meaning | Why It Matters |
|---|---|---|
| Deterministic | Same input always produces the same output | Otherwise you could never verify anything twice |
| Fixed-size output | Output length never changes, regardless of input size | Makes hashes easy to store, compare, and transmit |
| One-way (pre-image resistant) | Given a hash, you cannot feasibly find the input that produced it | Prevents reversing a hash back to secret data |
| Collision-resistant | Extremely hard to find two different inputs that produce the same hash | Prevents an attacker from swapping in fraudulent data that "looks the same" to the hash |
| Avalanche effect | Changing even one bit of input completely changes the output | Prevents attackers from making small, sneaky, undetected edits |
💡 A hash function is considered "broken" the moment researchers find a practical way to violate any of these properties — which is exactly what happened to older algorithms like MD5 and SHA-1, both of which had real collision attacks demonstrated. Neither should be used for security purposes today.
🏆 SHA-256: The Modern Standard¶
SHA-256 (part of the SHA-2 family, "Secure Hash Algorithm, 256-bit output") is the current widely trusted standard, used in TLS certificates, Bitcoin, password storage schemes, and file integrity verification everywhere.
- Output size: always 256 bits (64 hexadecimal characters), no matter the input size.
- No practical collision or pre-image attack is currently known against it.
The Avalanche Effect in Action¶
Here's what happens when you hash two almost identical inputs with SHA-256:
SHA-256("hello world")
-> b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
SHA-256("hello world!") <- only added one character
-> 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9
💥 A single added character (!) produces a completely unrelated-looking hash — no partial similarity, no predictable pattern. This is the avalanche effect: it guarantees that even a tiny, deliberate, one-bit tamper attempt is instantly and completely visible if you compare hashes.
💡 This is precisely why hashes are perfect for integrity checking: if you know the correct hash of a file in advance, you can hash any copy you receive and instantly know — with overwhelming confidence — whether it matches, byte for byte.
🔒 Why a Plain Hash Isn't Enough for Message Authentication¶
Suppose Alice sends Bob a message plus its SHA-256 hash, so Bob can verify nothing was altered in transit:
This looks secure — but there's a critical flaw: anyone can compute a SHA-256 hash. SHA-256 requires no secret key at all. So an active attacker (recall MITM attacks, 03-05: MITM Attacks & Defenses) can simply:
- Intercept the message.
- Change it:
"Transfer $100 to Bob"→"Transfer $10000 to Attacker". - Recompute a brand-new, matching hash for the tampered message.
- Forward both the fake message and its (correctly computed) fake hash to Bob.
Bob checks the hash, finds it matches the message he received, and has no way to know the original was different — because the attacker could compute a valid hash for any message, not just the real one.
💥 A plain hash proves "this data wasn't accidentally corrupted" (useful for things like download checksums), but it proves nothing about who sent it, because it doesn't involve any secret the attacker lacks.
🔐 HMAC: Adding a Secret Key to the Hash¶
HMAC (Hash-based Message Authentication Code) fixes this by mixing a shared secret key into the hashing process, so that only someone who knows the key can produce a valid tag.
Conceptually, HMAC hashes the message combined with the secret key (the real construction is more careful than simple concatenation, to resist specific mathematical attacks, but this captures the idea):
Alice and Bob share a secret key K in advance (e.g., derived via Diffie-Hellman,
see 07-05-diffie-hellman-key-exchange.md)
Alice sends: message = "Transfer $100 to Bob"
tag = HMAC(K, message)
Bob receives the message and tag, and independently computes:
expected_tag = HMAC(K, received_message)
If expected_tag == received tag -> message is authentic and untampered
If they don't match -> reject; something changed, or sender lacks the key
Now the earlier attack fails: the MITM attacker can still change the message, but they cannot compute a valid HMAC tag for the tampered message, because they don't know the secret key K. Bob's comparison will fail, and the forgery is caught.
| Plain Hash | HMAC | |
|---|---|---|
| Requires a secret key? | No | Yes |
| Anyone can compute it? | Yes | Only someone with the key |
| Proves integrity (data unmodified)? | Yes | Yes |
| Proves authenticity (came from a trusted party)? | No | Yes |
💡 HMAC is one of two common ways to add authenticity to hashing — the other is digital signatures, covered next in 07-07: Digital Signatures, which uses asymmetric crypto instead of a shared secret. HMAC needs both sides to already share a key (same limitation symmetric encryption has); digital signatures let anyone verify authenticity using only a public key.
📌 Key Takeaways¶
- A cryptographic hash function maps any input to a fixed-size output ("digest") that acts as a unique fingerprint for that data.
- Required properties: deterministic, fixed-size output, one-way (can't reverse it), collision-resistant (can't find two inputs with the same hash), and the avalanche effect (tiny input changes cause totally different output).
- SHA-256 is the current standard cryptographic hash; older algorithms like MD5 and SHA-1 are broken and should not be used for security.
- Changing even a single character of input completely changes the SHA-256 output — this avalanche effect is what makes hashing useful for detecting tampering.
- A plain hash alone does not prove authenticity — anyone, including an attacker, can compute a valid hash for any message, including a forged one.
- HMAC fixes this by mixing a shared secret key into the hash computation, so only someone who knows the key can produce a valid authentication tag.
- HMAC provides both integrity (data wasn't altered) and authenticity (it came from someone who holds the shared key) — a plain hash only provides the former.
- HMAC still requires a pre-shared secret key, just like symmetric encryption — the alternative that avoids this requirement is digital signatures, built on asymmetric crypto.