← Week 1: Cryptographic Foundations

Day 4: Hash Functions, HMAC, and HKDF

Phase 1 · May 17, 2026

← Week 1: Cryptographic Foundations

Agenda (2–3 hours)

  • Read (45 min): RFC 5869 (HKDF, ~12 pages — read the whole thing); RFC 8446 §7.1 (Key Schedule)
  • Study (45 min): HKDF construction and TLS 1.3 key schedule inputs
  • Practice (30 min): Trace HKDF calls manually
  • Challenge (45 min): Rust HKDF implementation with test vectors
← Week 1: Cryptographic Foundations

Hash Functions

SHA-256 and SHA-384 are the only hash functions in TLS 1.3.

Properties that matter:

  • Collision resistance: hard to find two inputs with the same output
  • Preimage resistance: given output, hard to find input
  • Avalanche effect: 1-bit input change → ~50% output bits change
  • Fixed output size: SHA-256 → 32 bytes, SHA-384 → 48 bytes

SHA-1 and MD5 are eliminated from TLS 1.3.

← Week 1: Cryptographic Foundations

HMAC

Hash-based Message Authentication Code — turns a hash function into a MAC.

HMAC(K, msg) = H((K ⊕ opad) || H((K ⊕ ipad) || msg))

Properties:

  • Requires knowledge of key K to compute or verify
  • Even if hash is broken (length extension), HMAC may still be secure
  • TLS 1.3 uses HMAC for the Finished message
← Week 1: Cryptographic Foundations

HKDF: Extract-then-Expand

Defined in RFC 5869. Two-phase KDF:

HKDF-Extract(salt, IKM) → PRK
  PRK = HMAC-Hash(salt, IKM)   # "extract" randomness into a pseudorandom key

HKDF-Expand(PRK, info, L) → OKM
  # Expand PRK into output keying material of length L
  # "info" binds the output to a specific context

TLS 1.3 wraps this in HKDF-Expand-Label:

HKDF-Expand-Label(Secret, Label, Context, Length)
  = HKDF-Expand(Secret, HkdfLabel, Length)
  where HkdfLabel = len || "tls13 " || Label || Context
← Week 1: Cryptographic Foundations

HKDF in the TLS 1.3 Key Schedule

Early Secret   = HKDF-Extract(0, PSK or 0)
Handshake Secret = HKDF-Extract(Derive-Secret(ES, "derived", ""), DHE)
Master Secret    = HKDF-Extract(Derive-Secret(HS, "derived", ""), 0)

client_hs_traffic_secret = Derive-Secret(HS, "c hs traffic", ClientHello..ServerHello)
server_hs_traffic_secret = Derive-Secret(HS, "s hs traffic", ClientHello..ServerHello)

Every traffic key is derived from a secret + transcript hash — binding keys to the specific handshake.

← Week 1: Cryptographic Foundations

Challenge Assignment

Implement HKDF-Extract and HKDF-Expand in Rust using the hkdf + hmac crates.
Verify your implementation against the RFC 5869 Appendix A test vectors.

hkdf = "0.12"
hmac = "0.12"
sha2 = "0.10"
hex = "0.4"

Then trace through the first two steps of the TLS 1.3 key schedule from RFC 8446 §7.1:
compute Early Secret and client_early_traffic_secret for the case where PSK = 0 (no PSK).
Use the RFC 8448 test vectors to verify your result.

← Week 1: Cryptographic Foundations

Resources