After this lesson, you will be able to: Explain cryptographic hash functions, collision and pre-image resistance, length-extension attacks, and why HMAC is the right way to authenticate with a secret.
Hash functions turn any input into a fixed-size fingerprint and are the workhorse of integrity, passwords, and signatures. This lesson covers SHA-256 and SHA-3, the security properties that make a hash cryptographic, length-extension attacks, and why you use HMAC instead of just hashing data with a secret.
A hash function maps any input to a fixed-size output (SHA-256 produces 256 bits) deterministically and one-way: you cannot reverse the output back to the input. The same input always gives the same hash; a tiny change gives a completely different hash. SHA-256 (from the SHA-2 family) is the current workhorse; SHA-3 is a newer, differently-constructed standard. MD5 and SHA-1 are broken and must not be used for security.
Three properties make a hash cryptographic. Pre-image resistance: given a hash, you cannot find an input that produces it. Second pre-image resistance: given an input, you cannot find a different input with the same hash. Collision resistance: you cannot find any two inputs with the same hash. MD5 and SHA-1 fell to collision attacks (researchers produced two different files with the same hash), which is why they are retired for signatures and certificates.
A natural but wrong way to authenticate a message with a secret is hash(secret + message). Many hash constructions (including SHA-256's Merkle-Damgard design) are vulnerable to length-extension: an attacker who knows the hash and message length can append data and compute a valid hash for the extended message without knowing the secret. HMAC (Hash-based Message Authentication Code) is the correct construction: it nests the key into the hashing in a way that resists length extension. Always use HMAC to authenticate with a shared secret, never a naive concatenation.
Authenticate a message with a shared secret using HMAC-SHA256.
import crypto from "crypto";const secret = process.env.HMAC_SECRET!;const message = "transfer:100:to:bob";// CORRECT: HMAC resists length-extensionconst mac = crypto.createHmac("sha256", secret).update(message).digest("hex");// Verify with a constant-time comparison to avoid timing attacksfunction verify(message: string, tag: string) {const expected = crypto.createHmac("sha256", secret).update(message).digest();return crypto.timingSafeEqual(Buffer.from(tag, "hex"), expected);}
Pick one.
Using MD5 or SHA-1 for anything security-relevant. Using hash(secret + message) instead of HMAC. Using a fast hash for passwords. Comparing MACs with == (timing attack); use a constant-time compare. Treating a hash as encryption (it is one-way, not reversible). Forgetting that a hash alone provides integrity only if the hash itself is protected (otherwise an attacker changes both).
Sign in and purchase access to unlock this lesson.