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