Part 0 · 2 chapters · ~12 min

Hashes and MACs

Cryptographic hash properties, SHA-2, SHA-3 and BLAKE3, broken hashes (MD5, SHA-1), why hashing guessable data is not anonymisation, length-extension attacks, MACs and HMAC, constant-time comparison, replay protection, and choosing hash, MAC or signature.

1

Hashes

code
echo -n 'transfer:500000:NGN' | shasum -a 256
node -e "console.log(require('crypto').createHash('sha256').update('transfer:500000:NGN').digest('hex'))"
// measured here: SHA-256 over 64 MB in 34 ms (1,883 MB/s)
WHAT A CRYPTOGRAPHIC HASH GUARANTEES
SHA-256: any input to 256 bits, measured at 1,883 MB/s here
deterministicSame input, same digest, everytime, on every machine.preimage resistanceGiven a digest, you cannot find aninput that produces it.second preimageGiven an input, you cannot find adifferent one with the samedigest.collision resistanceYou cannot find any two inputswith the same digest.avalancheChange one bit of input and abouthalf the output bits change.not a secretAnyone can compute it: a hashalone proves nothing about whomade it.
swipe the figure sideways, or tap expand for full screen
1/5
fixed-size fingerprints
A hash maps any input to a fixed-size digest (32 bytes for SHA-256). Hashing 64 MB took 34 ms on this machine: about 1.9 GB/s.
any input → 32 bytes1,883 MB/s measured
2

MACs and HMAC

code
import { createHmac, timingSafeEqual } from 'node:crypto';
const sign = (key: Buffer, ts: string, body: Buffer) => createHmac('sha256', key).update(ts + '.').update(body).digest();
function verify(key: Buffer, ts: string, body: Buffer, sigHex: string) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;           // reject old messages (replay)
  const expected = sign(key, ts, body), got = Buffer.from(sigHex, 'hex');
  return got.length === expected.length && timingSafeEqual(got, expected);    // constant time
}

Why not just hash(key + message)? SHA-256 and SHA-512 are vulnerable to length extension: knowing hash(key ‖ m), an attacker can compute hash(key ‖ m ‖ padding ‖ extra) without the key. HMAC's nested construction prevents this; so do SHA-3 and BLAKE3 keyed modes.

HMAC: INTEGRITY WITH A SHARED SECRET
a webhook sender and receiver who share a key
processornetworkyour webhook endpointtag = HMAC-SHA256(key, timestamp + body)
swipe the figure sideways, or tap expand for full screen
1/4
the tag
The sender computes a MAC over the message with a secret key both sides share. HMAC (built from a hash) is the standard construction.
MAC = keyed hash over the messageHMAC-SHA256 is the default