L26 · HMAC-DRBG & Stream Ciphers

CS 161, Lesson 26, in 51 slides. It covers HMAC-DRBG, a secure pseudorandom generator built from HMAC, along with its Seed and Generate algorithms, its absorption of low-entropy input, and its rollback resistance, in section 9.4, then the Dual_EC_DRBG backdoor as enrichment. It closes with stream ciphers in section 9.5: the keystream used as a one-time pad, the formal Enc and Dec scheme, the roughly 2^64-bit limit on AES-CTR, and ChaCha20's counter-driven random access. It is anchored to textbook sections 9.4 to 9.5.

Subject: Computer Security · 82 slides · applied lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. From Randomness to a Cipher

Title

CS 161 · Lesson 26 of 45

HMAC-DRBG · seeding & generation · rollback resistance · stream ciphers · AES-CTR · ChaCha20

2. By the end of this lesson you can…

Objectives

  1. Describe HMAC-DRBG — a secure pRNG built from HMAC — and run its Seed, Reseed, and Generate algorithms over the internal state (K, V).
  2. Explain why HMAC-DRBG is good: it absorbs low-entropy seeds, extra bytes never hurt, and it is rollback-resistant.
  3. Define a stream cipher as a pseudorandom keystream XOR-ed with the message — the one-time pad from L19, now from a secure pRNG.
  4. Encrypt and decrypt with the scheme Enc(K,M)=⟨IV, PRNG(K,IV)⊕M⟩ and explain why each message needs a fresh IV.
  5. Compare AES-CTR (a stream cipher with a ~2^64-bit-per-key limit) and ChaCha20 (a counter giving random access), and reason about the rollback-resistance trade-off.

3. What survived from L25 · Randomness, Entropy, pRNGs & Rollback Resistance?

Warm-up

Discussion prompt

Before we open L26 · HMAC-DRBG & Stream Ciphers: without looking back, what was the main idea of L25 · Randomness, Entropy, pRNGs & Rollback Resistance, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

CS 161, Lesson 25, in 50 slides. It explains what randomness and entropy mean for cryptography, in section 9.1, then covers the pseudorandom generator with its Seed, Reseed, and Generate interface and the computational indistinguishability it aims at, in sections 9.1 and 9.2, and rollback resistance against state compromise in section 9.3. It adds real-world entropy failures - VM snapshots, the Debian OpenSSL bug, and weak entropy at boot - flagged as enrichment. It is anchored to textbook sections 9.1 to 9.3.

4. Three questions this lesson answers

Concept

L25 told us what a secure pRNG must do. Now we build one from a tool we already trust — HMAC (L23) — and then turn its output into a working cipher.

How do we build a pRNG?
§9.4 HMAC-DRBG, from HMAC and state (K, V)
Why is it a good one?
§9.4 low-entropy seeds, rollback resistance
How does it encrypt?
§9.5 stream ciphers — pRNG output as a one-time pad

5. Which is which: Three questions this lesson answers

Matching

Match the pairs

From Three questions this lesson answers — match each one to what it actually does. The descriptions have been shuffled.

  • c1. How do we build a pRNG?
  • c2. Why is it a good one?
  • c3. How does it encrypt?
  • b1. §9.4 HMAC-DRBG, from HMAC and state (K, V)
  • b2. §9.4 low-entropy seeds, rollback resistance
  • b3. §9.5 stream ciphers — pRNG output as a one-time pad

Why: How do we build a pRNG?, Why is it a good one?, How does it encrypt? are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.

6. Building a pRNG: HMAC-DRBG

Section

Part 1 · §9.4 the construction

7. §9.4 A scenario: turn HMAC into randomness

Concept

You have HMAC from L23 — a keyed function whose output is indistinguishable from random to anyone without the key. You need a pRNG: feed it a seed, get back an endless stream of random-looking bits. How do you bridge the two?

HMAC-DRBG does exactly that. DRBG = Deterministic Random Bit Generator: deterministic given its seed, but its output looks random to anyone who can't see the secret internal state.

8. §9.4 The internal state: two values K and V

Concept

HMAC-DRBG — A secure pRNG that builds an output stream by repeatedly applying HMAC. Its entire secret is an internal state of two values, K and V.

The two values are exactly the two arguments HMAC takes. Generate them, feed them back in, and you get a fresh pseudorandom block each round.

9. Break it if you can: §9.4 The internal state: two values K and V

Counterexample

Discussion prompt

The two values are exactly the two arguments HMAC takes. Generate them, feed them back in, and you get a fresh pseudorandom block each round.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

10. §9.4 Why repeated HMAC looks random

Intuition

HMAC's whole promise (L23): without the key, its output is indistinguishable from random. HMAC-DRBG just leans on that promise over and over.

Each output block is one more HMAC of the previous block under the secret key K. To Eve, who never sees K or V, every block is one more random-looking string — and so is the entire stream.

Ask yourself: what is the one thing that must stay hidden for the stream to look random? (The internal state — K and V. Leak it and Eve can recompute every future output.)

11. By analogy: §9.4 Why repeated HMAC looks random

Analogy

Discussion prompt

Explain §9.4 Why repeated HMAC looks random by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

HMAC's whole promise (L23): without the key, its output is indistinguishable from random. HMAC-DRBG just leans on that promise over and over.

12. §9.4 The Generate algorithm

Concept

Generate(n) produces n bits of output by chaining HMAC on V, then ratchets the state forward so the next call is independent.

Generate(n):
    output = ""
    while len(output) < n:
        V = HMAC(K, V)
        output = output ‖ V
    # ratchet the state forward
    K = HMAC(K, V ‖ 0x00)
    V = HMAC(K, V)
    return output[0:n]

‖ means concatenation. Each loop turn appends one fresh HMAC block to the output; the final two lines refresh K and V so a later Generate can't be linked to this one.

13. Teach it back: §9.4 The Generate algorithm

Explain it

Discussion prompt

Explain §9.4 The Generate algorithm to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

Generate(n) produces n bits of output by chaining HMAC on V, then ratchets the state forward so the next call is independent.

14. What has to happen first: §9.4 Trace two Generate iterations

Ranking

Put in order

Put the moves of §9.4 Trace two Generate iterations into the order they have to happen.

  1. Start a Generate call with state (K, V) and an empty output
  2. Round 1: set V = HMAC(K, V), append V to output
  3. Round 2: set V = HMAC(K, V) again, append it
  4. Verify: each block is HMAC of the previous, so each is indistinguishable from random

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. We want enough output that the while-loop runs at least twice; each turn computes one HMAC block.

15. §9.4 Trace two Generate iterations

Worked example

Start a Generate call with state (K, V) and an empty output

Why: We want enough output that the while-loop runs at least twice; each turn computes one HMAC block.

Round 1: set V = HMAC(K, V), append V to output

Why: Call this block V_1 = HMAC(K, V_0). The output so far is exactly V_1.

Round 2: set V = HMAC(K, V) again, append it

Why: Now V_2 = HMAC(K, V_1). The output is V_1 ‖ V_2 — two chained HMAC blocks under the same K.

roundV = HMAC(K, V)output so far
startV_0(empty)
1V_1 = HMAC(K, V_0)V_1
2V_2 = HMAC(K, V_1)V_1 ‖ V_2

Verify: each block is HMAC of the previous, so each is indistinguishable from random

Why: §9.4: as long as K and V stay secret, V_1 and V_2 each look random — so does their concatenation, which is the returned output.

16. Fill in: V = HMAC(K, V) for §9.4 Trace two Generate iterations

Comparison

Comparison matrix

From §9.4 Trace two Generate iterations: refill the V = HMAC(K, V) column from what you know. The rest of the table is as it appeared.

roundV = HMAC(K, V)output so far
startV_0(empty)
1V_1 = HMAC(K, V_0)V_1
2V_2 = HMAC(K, V_1)V_1 ‖ V_2

17. Plan first: §9.4 Walk the state ratchet after Generate

Step zero

Discussion prompt

§9.4 Walk the state ratchet after Generate — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: After filling the output, refresh K: K = HMAC(K, V ‖ 0x00)

Answer:

  1. After filling the output, refresh K: K = HMAC(K, V ‖ 0x00)
  2. Refresh V under the NEW K: V = HMAC(K, V)
  3. Verify: the next Generate starts from (K_new, V_new), unlinkable to this one

18. §9.4 Walk the state ratchet after Generate

Worked example

After filling the output, refresh K: K = HMAC(K, V ‖ 0x00)

Why: The old K is replaced by a new HMAC value, so the key that produced this call's output no longer governs the next one.

Refresh V under the NEW K: V = HMAC(K, V)

Why: V is re-derived using the freshly updated K, decoupling the next Generate's starting point from this one.

stepKV
after output loopK_oldV_last
K = HMAC(K, V ‖ 0x00)K_newV_last
V = HMAC(K, V)K_newV_new

Verify: the next Generate starts from (K_new, V_new), unlinkable to this one

Why: §9.4: ratcheting both values forward through HMAC means a later call's output can't be tied back to this call — and supports rollback resistance.

19. What each one costs: §9.4 Walk the state ratchet after Generate

Trade off

Comparison matrix

From §9.4 Walk the state ratchet after Generate: every row here is a choice with a cost. Fill the K column, then say which row you would actually pick and what you give up for it.

stepKV
after output loopK_oldV_last
K = HMAC(K, V ‖ 0x00)K_newV_last
V = HMAC(K, V)K_newV_new

20. §9.4 The Seed and Reseed algorithms

Concept

Seed(s) initializes the state from a seed string s. Reseed(s) mixes in fresh entropy later — it is the same steps but without resetting K and V to 0 first.

Seed(s):
    K = 0
    V = 0
    K = HMAC(K, V ‖ s ‖ 0x00)
    V = HMAC(K, V)
    K = HMAC(K, V ‖ s ‖ 0x01)
    V = HMAC(K, V)

# Reseed(s): identical, but DO NOT reset K, V to 0 first

The bytes 0x00 and 0x01 are domain separators — their exact values don't matter. The point is the same one throughout: lots of HMAC outputs ⇒ a state that's indistinguishable from random.

21. §9.4 Reseed keeps the old entropy

Intuition

Seed wipes K and V to 0 and starts fresh. Reseed deliberately skips that wipe — it folds the new seed into the existing state instead of throwing it away.

That's what you want from a long-running generator: as new entropy trickles in (timing jitter, device noise), each Reseed makes the state strictly harder to guess, never weaker.

Ask yourself: if Reseed never discards old entropy and adds new, can mixing in fresh bytes ever hurt? (No — and that 'never hurts' property is the next big idea.)

22. Why HMAC-DRBG Is Good

Section

Part 2 · §9.4 properties

23. §9.4 It absorbs long, low-entropy seeds

Concept

Real entropy sources are often weak per bit — a noisy sensor might give only a fraction of a bit of true randomness per sampled bit. HMAC-DRBG fixes this by absorbing an arbitrarily long seed.

If each input bit carries about 0.1 bits of real entropy, then 2560 bits of seed yield roughly 256 bits of real entropy in the state — plenty for a secure key. Just feed in enough low-quality bits.

24. What has to happen first: §9.4 Sizing a low-entropy seed

Ranking

Put in order

Put the moves of §9.4 Sizing a low-entropy seed into the order they have to happen.

  1. Decide how much real entropy the state needs
  2. Measure the entropy rate of the source: ~0.1 bits of real entropy per input bit
  3. Feed all 2560 bits in as the seed
  4. Verify: the state now holds ~256 bits of real entropy

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. A 256-bit security level needs about 256 bits of true entropy in the state — the standard target.

25. §9.4 Sizing a low-entropy seed

Worked example

Decide how much real entropy the state needs

Why: A 256-bit security level needs about 256 bits of true entropy in the state — the standard target.

Measure the entropy rate of the source: ~0.1 bits of real entropy per input bit

Why: A weak source (e.g. lightly-debiased hardware noise) gives a small fraction of a bit per sample.

\[ \text{seed bits needed} = \frac{256\ \text{bits}}{0.1\ \text{bits/bit}} = 2560\ \text{bits} \]

Feed all 2560 bits in as the seed

Why: HMAC-DRBG compresses the whole seed through HMAC, concentrating its scattered entropy into the state.

Verify: the state now holds ~256 bits of real entropy

Why: §9.4: a long low-entropy seed yields a strong state — the generator absorbs as many weak bits as you give it.

26. Decode the notation: §9.4 Sizing a low-entropy seed

Notation

Annotate

From §9.4 Sizing a low-entropy seed — read this one piece at a time. What is each part doing?

On: \( \text{seed bits needed} = \frac{256\ \text{bits}}{0.1\ \text{bits/bit}} = 2560\ \text{bits} \)

  • A 256-bit security level needs about 256 bits of true entropy in the state — the standard target.
  • A weak source (e.g. lightly-debiased hardware noise) gives a small fraction of a bit per sample.
  • HMAC-DRBG compresses the whole seed through HMAC, concentrating its scattered entropy into the state.

27. §9.4 Extra (even zero-entropy) input never hurts

Concept

Adding more input to the seed — even input with no entropy at all, like a string of all-zeros — can only ever help or do nothing. It never weakens the state.

So when in doubt, mix more in. There is no penalty for over-seeding, and a real upside if any of those extra bytes turn out to carry entropy you underestimated.

28. §9.4 It is rollback-resistant

Concept

Rollback resistance — An attacker who compromises the CURRENT state cannot compute any PREVIOUS state — and so cannot recover earlier outputs.

Reversing HMAC-DRBG would mean running the underlying hash backwards — inverting a one-way function. That is infeasible, so past outputs stay safe even after a state compromise.

29. Take the definitions apart: HMAC-DRBG vs Rollback resistance

Definition probe

Sort into buckets

Every line below is part of the definition of HMAC-DRBG or of Rollback resistance — one or the other, never both. Put each where it belongs.

HMAC-DRBG
A secure pRNG that builds an output stream by repeatedly applying HMAC.; Its entire secret is an internal state of two values, K and V.
Rollback resistance
An attacker who compromises the CURRENT state cannot compute any PREVIOUS state; and so cannot recover earlier outputs.
b1
A secure pRNG that builds an output stream by repeatedly applying HMAC. Its entire secret is an internal state of two values, K and V.
b2
An attacker who compromises the CURRENT state cannot compute any PREVIOUS state — and so cannot recover earlier outputs.

30. §9.4 Why you can't run the ratchet backwards

Intuition

Each state update is K = HMAC(K, V‖…) and V = HMAC(K, V). HMAC is built on a one-way hash: easy forwards, infeasible to invert.

So even Eve holding the full current (K, V) hits a wall trying to go back one step — she'd have to invert the hash to find the previous K and V. Yesterday's keystream is out of reach.

Ask yourself: which textbook property of the hash does rollback resistance rely on? (One-wayness — the same property that protects a hashed password.)

31. Something is wrong here: 'extra non-random bytes weaken HMAC-DRBG'

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student: 'If I pad the seed with junk like all-zeros, I'm diluting the real entropy — that must make the generator weaker.'

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Backwards. HMAC mixes everything together; adding input — even all-zeros — can only preserve or increase the unpredictability of the state, never reduce it.

A student: what does mixing in extra bytes actually do?

Why: Backwards. HMAC mixes everything together; adding input — even all-zeros — can only preserve or increase the unpredictability of the state, never reduce it.

32. Trap: 'extra non-random bytes weaken HMAC-DRBG'

Trap

The trap

A student: 'If I pad the seed with junk like all-zeros, I'm diluting the real entropy — that must make the generator weaker.'

Assume zero-entropy input lowers the state's security

Why: Backwards. HMAC mixes everything together; adding input — even all-zeros — can only preserve or increase the unpredictability of the state, never reduce it.

The fix

A student: what does mixing in extra bytes actually do?

Feed in as much as you like — extra input never hurts

Why: §9.4: HMAC-DRBG absorbs arbitrarily long seeds and zero-entropy strings never weaken the state. Over-seeding is safe and often wise.

33. §9.4 ⊕ Dual_EC_DRBG: a DRBG with a backdoor

Concept

⊕ Supplemental — not in this textbook section. Not every standardized DRBG is trustworthy. Dual_EC_DRBG was a NIST-standardized generator later shown to contain a likely NSA backdoor.

Its design used special elliptic-curve constants. Whoever chose those constants could hold a secret value that lets them predict the generator's future output after seeing a little of it — a deliberate trapdoor.

34. §9.4 ⊕ The governance lesson: prefer transparent designs

Intuition

⊕ The math of Dual_EC_DRBG wasn't broken by accident — it was built to be breakable by its designer. The defense isn't cleverer math; it's transparency.

This is Kerckhoff and Shannon again: a design should be fully public and analyzable, with no secret constants whose origin you must trust. HMAC-DRBG's pieces (HMAC, a public hash) are exactly that kind of open, well-studied construction.

Ask yourself: why prefer HMAC-DRBG over a generator with unexplained magic constants? (Because every part of HMAC-DRBG is public and analyzable — there's nowhere to hide a backdoor.)

35. Encrypting a Stream: Stream Ciphers

Section

Part 3 · §9.5 the scheme

36. §9.5 A scenario: encrypt bits as they arrive

Concept

A block cipher waits for a full block before it can act. But sometimes data arrives one bit at a time — a live audio feed, a streaming socket. You want to encrypt each bit the instant it shows up.

Stream cipher — A cipher that encrypts and decrypts a message as it arrives, one bit at a time, rather than waiting for a full block as a block cipher does.

37. Term to definition: L26 · HMAC-DRBG & Stream Ciphers

Matching

Match the pairs

Match each term to the definition this lesson gave it — not the one you would guess from the word.

  • t1. HMAC-DRBG
  • t2. Rollback resistance
  • t3. Stream cipher
  • d1. A secure pRNG that builds an output stream by repeatedly applying HMAC. Its entire secret is an internal state of two values, K and V.
  • d2. An attacker who compromises the CURRENT state cannot compute any PREVIOUS state — and so cannot recover earlier outputs.
  • d3. A cipher that encrypts and decrypts a message as it arrives, one bit at a time, rather than waiting for a full block as a block cipher does.

Why: These are the working definitions of HMAC-DRBG, Rollback resistance, Stream cipher as L26 · HMAC-DRBG & Stream Ciphers uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.

38. §9.5 The idea: a one-time pad from a pRNG

Concept

The trick is the one-time pad from L19: XOR each plaintext bit with a secret keystream bit. The only problem with the OTP was needing a truly random pad as long as the message.

A stream cipher solves that: use a secure pRNG's output as the keystream. The secret key is just the pRNG seed — short to share, but it expands into a pad as long as you need.

39. §9.5 A stream cipher IS the OTP, with a generated pad

Intuition

Picture the L19 one-time pad, but instead of pre-sharing a giant random pad, both sides grow the pad on demand from a shared seed by running the same pRNG.

Because the keystream is indistinguishable from random (that's the pRNG's job), XOR-ing it onto the message hides the message just like a real OTP would — but you only had to share a short key.

Ask yourself: what replaced the OTP's true-random pad here? (A pRNG's pseudorandom output — same XOR, same one-time-use rule, far less key to share.)

40. Where does each piece belong: L26 · HMAC-DRBG & Stream Ciphers

Sorting

Sort into buckets

These are the pieces of L26 · HMAC-DRBG & Stream Ciphers, out of order. Put each one back under the part of the lesson it belongs to.

Building a pRNG: HMAC-DRBG
§9.4 A scenario: turn HMAC into randomness; §9.4 The internal state: two values K and V; §9.4 Why repeated HMAC looks random
Why HMAC-DRBG Is Good
§9.4 It absorbs long, low-entropy seeds; §9.4 Sizing a low-entropy seed; §9.4 Extra (even zero-entropy) input never hurts
Encrypting a Stream: Stream Ciphers
§9.5 A scenario: encrypt bits as they arrive; §9.5 The idea: a one-time pad from a pRNG; §9.5 A stream cipher IS the OTP, with a generated pad
s1
Building a pRNG: HMAC-DRBG is where L26 · HMAC-DRBG & Stream Ciphers puts §9.4 A scenario: turn HMAC into randomness, §9.4 The internal state: two values K and V, §9.4 Why repeated HMAC looks random. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
Why HMAC-DRBG Is Good is where L26 · HMAC-DRBG & Stream Ciphers puts §9.4 It absorbs long, low-entropy seeds, §9.4 Sizing a low-entropy seed, §9.4 Extra (even zero-entropy) input never hurts. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
Encrypting a Stream: Stream Ciphers is where L26 · HMAC-DRBG & Stream Ciphers puts §9.5 A scenario: encrypt bits as they arrive, §9.5 The idea: a one-time pad from a pRNG, §9.5 A stream cipher IS the OTP, with a generated pad. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

41. §9.5 The stream-cipher scheme, formally

Concept

Seed the pRNG with the key K and a fresh random IV, take its output as the keystream, and XOR. The IV is sent in the clear alongside the ciphertext.

\[ \textbf{Enc}(K, M) = \langle\, IV,\ \mathrm{PRNG}(K, IV) \oplus M \,\rangle \]

\[ \textbf{Dec}(K, IV, C) = \mathrm{PRNG}(K, IV) \oplus C \]

Decryption regenerates the same keystream from K and the received IV, then XORs it off — exactly OTP decryption.

42. §9.5 Why a fresh IV per message

Intuition

Seeding only with K would make every message use the identical keystream — and reusing a one-time pad is the two-time-pad disaster from L19/L21.

A fresh random IV per message makes PRNG(K, IV) different each time, so no two messages share a pad. The IV is public; only K is secret.

Ask yourself: what L19 leak returns the instant two messages share a keystream? (C ⊕ C′ = M ⊕ M′ — the two-time-pad break.)

43. Plan first: §9.5 Encrypt then decrypt a 4-bit message

Step zero

Discussion prompt

§9.5 Encrypt then decrypt a 4-bit message — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.

Hint: It starts with: Take M = 1101 and a keystream PRNG(K, IV) = 1011

Answer:

  1. Take M = 1101 and a keystream PRNG(K, IV) = 1011
  2. Encrypt: C = keystream ⊕ M, bit by bit
  3. Decrypt: regenerate the same keystream from K and IV, then XOR it onto C
  4. Verify: the recovered bits 1101 equal the original M

44. §9.5 Encrypt then decrypt a 4-bit message

Worked example

Take M = 1101 and a keystream PRNG(K, IV) = 1011

Why: The pRNG, seeded with the secret key K and this message's IV, emitted the 4-bit keystream 1011.

Encrypt: C = keystream ⊕ M, bit by bit

Why: XOR each plaintext bit with the keystream bit below it — exactly a one-time pad with a generated pad.

bit jkeystreamm_jc_j = ks_j ⊕ m_j
1110
2011
3101
4110

\[ C = 1011 \oplus 1101 = 0110 \]

Decrypt: regenerate the same keystream from K and IV, then XOR it onto C

Why: Dec = PRNG(K, IV) ⊕ C; the receiver derives the identical 1011 keystream from the public IV and secret K.

\[ M = 1011 \oplus 0110 = 1101 \]

Verify: the recovered bits 1101 equal the original M

Why: §9.5: keystream ⊕ keystream = 0 by self-inverse, so XOR-ing the same pad twice returns M — the round trip is lossless.

45. Fill in: c_j = ks_j ⊕ m_j for §9.5 Encrypt then decrypt a 4-bit message

Comparison

Comparison matrix

From §9.5 Encrypt then decrypt a 4-bit message: refill the c_j = ks_j ⊕ m_j column from what you know. The rest of the table is as it appeared.

bit jkeystreamm_jc_j = ks_j ⊕ m_j
1110
2011
3101
4110

46. §9.5 The key is the seed; the IV is public

Concept

Two parties share only the short secret key K. For each message the sender draws a fresh random IV, seeds PRNG(K, IV), and the keystream is born — no giant pre-shared pad required.

The IV rides along in the clear with the ciphertext. The receiver re-seeds with the same K and IV, regenerates the identical keystream, and XORs it off. Secrecy rests entirely on K; the IV only needs to be fresh, not secret.

47. Something is wrong here: 'reusing the keystream is fine'

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student: 'Generating a new IV each time is a hassle — I'll just seed the pRNG with K and reuse the same keystream for every message.'

It is wrong. Say what breaks — and say it before you turn the page.

Correct: This is the two-time pad (L19/L21).

A student: how do I keep every message's keystream distinct?

Why: This is the two-time pad (L19/L21). The shared keystream cancels and C ⊕ C′ = M ⊕ M′ leaks the plaintexts' XOR — confidentiality is gone.

48. Trap: 'reusing the keystream is fine'

Trap

The trap

A student: 'Generating a new IV each time is a hassle — I'll just seed the pRNG with K and reuse the same keystream for every message.'

\[ C \oplus C' = (ks \oplus M) \oplus (ks \oplus M') = M \oplus M' \]

Reuse one keystream across two messages

Why: This is the two-time pad (L19/L21). The shared keystream cancels and C ⊕ C′ = M ⊕ M′ leaks the plaintexts' XOR — confidentiality is gone.

The fix

A student: how do I keep every message's keystream distinct?

Seed with K and a FRESH random IV per message

Why: §9.5: PRNG(K, IV) differs for each IV, so no two messages share a pad. The IV travels in the clear; the keystream is one-time, just like the OTP demands.

49. Decode the notation: Trap: 'reusing the keystream is fine'

Notation

Annotate

From Trap: 'reusing the keystream is fine' — read this one piece at a time. What is each part doing?

On: \( C \oplus C' = (ks \oplus M) \oplus (ks \oplus M') = M \oplus M' \)

  • This is the two-time pad (L19/L21). The shared keystream cancels and C ⊕ C′ = M ⊕ M′ leaks the plaintexts' XOR — confidentiality is gone.
  • §9.5: PRNG(K, IV) differs for each IV, so no two messages share a pad. The IV travels in the clear; the keystream is one-time, just like the OTP demands.

50. A Block Cipher as a Stream: AES-CTR

Section

Part 4 · §9.5 AES-CTR

51. §9.5 AES-CTR is effectively a stream cipher

Concept

Recall CTR mode from L20: encrypt a running counter under AES and XOR the result onto the plaintext. That AES-of-the-counter output is precisely a keystream — so AES-CTR behaves as a stream cipher.

Technically AES is a pseudorandom permutation, not a generator. But in practice its CTR keystream is indistinguishable from a pRNG's output — provided you don't push one key too far.

52. §9.5 The ~2^64-bit-per-key limit

Concept

The keystream looks random only while you encrypt at most about 2^64 bits under a single key. Beyond that, CTR's block structure starts to show.

Because AES is a permutation, two distinct counter inputs never give the same AES output — but a true pRNG sometimes would. Past ~2^64 bits, the absence of such collisions is itself detectable, leaking that the underlying plaintext blocks differ.

53. §9.5 Permutation vs generator: where CTR drifts

Intuition

A real random generator occasionally repeats an output block — birthday-style collisions. A permutation like AES never repeats for distinct inputs. For a while that difference is invisible.

But after enough blocks (~2^64 bits), an attacker expects to have seen some collisions from a true pRNG and sees none from AES-CTR. That mismatch is a distinguisher — so rotate keys well before the limit.

Ask yourself: what's the fix when you approach the limit? (Re-key — start a fresh AES key so the block count under any one key stays well below ~2^64 bits.)

54. §9.5 CTR turns a block cipher into a keystream

Intuition

AES alone scrambles one fixed-size block. CTR mode encrypts a running counter (0, 1, 2, …) under AES and uses those outputs as keystream blocks — so the message never passes through AES directly; only the counter does.

That's why CTR is a stream cipher in disguise: the plaintext is only ever XOR-ed with AES(counter). Encryption and decryption are the identical XOR, exactly like the one-time pad.

Ask yourself: in AES-CTR, what plays the role of PRNG(K, IV) from the stream-cipher scheme? (The sequence AES_K(IV), AES_K(IV+1), … — the keystream.)

55. Something is wrong here: 'one CTR key can encrypt unlimited data'

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student: 'AES-CTR is a stream cipher, so I can stream a petabyte through one key forever — no IV reuse, so no problem.'

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Even with unique counters, past ~2^64 bits the permutation's lack of collisions becomes detectable, leaking that plaintext blocks differ.

A student: how much data is safe under one CTR key?

Why: Even with unique counters, past ~2^64 bits the permutation's lack of collisions becomes detectable, leaking that plaintext blocks differ. The keystream stops looking random.

56. Trap: 'one CTR key can encrypt unlimited data'

Trap

The trap

A student: 'AES-CTR is a stream cipher, so I can stream a petabyte through one key forever — no IV reuse, so no problem.'

Assume a single CTR key is safe for unbounded data

Why: Even with unique counters, past ~2^64 bits the permutation's lack of collisions becomes detectable, leaking that plaintext blocks differ. The keystream stops looking random.

The fix

A student: how much data is safe under one CTR key?

Keep total data under one key below ~2^64 bits, then re-key

Why: §9.5: AES is a permutation, not a generator; the indistinguishability guarantee holds only below ~2^64 bits. Rotate keys before you reach it.

57. A Counter-Native Cipher: ChaCha20

Section

Part 5 · §9.5 ChaCha20

58. §9.5 Why a generic pRNG can't seek

Concept

HMAC-DRBG's output is a forward-only chain: block i comes from block i−1, which came from i−2, and so on. There is no shortcut to block i — you must walk every step before it.

For encrypting a stream that's fine, but it makes seeking expensive: to decrypt one chunk deep in a file you'd regenerate all the keystream before it. A stream cipher built for large files wants something better.

59. Teach it back: §9.5 Why a generic pRNG can't seek

Explain it

Discussion prompt

Explain §9.5 Why a generic pRNG can't seek to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

HMAC-DRBG's output is a forward-only chain: block i comes from block i−1, which came from i−2, and so on. There is no shortcut to block i — you must walk every step before it.

60. §9.5 ChaCha20 puts a counter in the keystream

Concept

Dedicated stream ciphers like ChaCha20 are designed to compute their keystream directly from the key, a nonce, and a block counter — the position is an explicit input, not something you reach by iterating.

So keystream block number i is a direct function of i: you can compute it without computing blocks 0 through i−1 first.

61. §9.5 Random access: jump into a 1 TB file

Intuition

Say you want to decrypt one chunk in the middle of an encrypted 1 TB file. With a generic pRNG like HMAC-DRBG you'd have to generate all the keystream up to that point — its state only moves forward, one step at a time.

With ChaCha20 you just set the counter to that chunk's block index and compute its keystream directly. That's random access — jump anywhere instantly.

Ask yourself: why can't HMAC-DRBG do the same jump? (Its output is a forward-only chain — block i depends on running the ratchet i times; there's no formula for block i alone.)

62. By analogy: §9.5 Random access: jump into a 1 TB file

Analogy

Discussion prompt

Explain §9.5 Random access: jump into a 1 TB file by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

With ChaCha20 you just set the counter to that chunk's block index and compute its keystream directly. That's random access — jump anywhere instantly.

63. What has to happen first: §9.5 Why the counter beats a forward-only pRNG

Ranking

Put in order

Put the moves of §9.5 Why the counter beats a forward-only pRNG into the order they have to happen.

  1. Goal: decrypt block #1,000,000 of an encrypted file
  2. HMAC-DRBG approach: run Generate from the start until you reach block 1,000,000
  3. ChaCha20 approach: set the block counter = 1,000,000 and compute that keystream block directly
  4. Verify: the counter design makes mid-file decryption O(1) instead of O(i)

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. We need only the keystream for that one block — ideally without touching the other 999,999.

64. §9.5 Why the counter beats a forward-only pRNG

Worked example

Goal: decrypt block #1,000,000 of an encrypted file

Why: We need only the keystream for that one block — ideally without touching the other 999,999.

HMAC-DRBG approach: run Generate from the start until you reach block 1,000,000

Why: Its state advances one HMAC at a time, so reaching block i means computing all i prior blocks — costly for large i.

ChaCha20 approach: set the block counter = 1,000,000 and compute that keystream block directly

Why: The counter is an input to the keystream function, so block i is a direct computation — no prior blocks needed.

cipherwork to reach block irandom access?
HMAC-DRBGcompute all i blocksno — forward only
ChaCha20 / AES-CTRcompute block i directlyyes — set the counter

Verify: the counter design makes mid-file decryption O(1) instead of O(i)

Why: §9.5: a counter in the keystream computation is exactly what lets a stream cipher seek — the property a generic pRNG lacks.

65. Fill in: random access? for §9.5 Why the counter beats a forward-only…

Comparison

Comparison matrix

From §9.5 Why the counter beats a forward-only pRNG: refill the random access? column from what you know. The rest of the table is as it appeared.

cipherwork to reach block irandom access?
HMAC-DRBGcompute all i blocksno — forward only
ChaCha20 / AES-CTRcompute block i directlyyes — set the counter

66. §9.5 The trade-off: no rollback resistance

Concept

Viewed as a pRNG, AES-CTR and ChaCha20 lack rollback resistance: the 'state' is just the key plus the counter, and from the current state you can compute every earlier block by simply decrementing the counter.

But that's not a flaw here — it's the same property, seen from the other side. The very jump-forward (and jump-back) ability that loses rollback resistance is exactly what makes these good stream ciphers.

67. §9.5 Different jobs want different properties

Intuition

HMAC-DRBG is built for a generator's job: emit secrets where past outputs must stay safe even after a compromise — so rollback resistance matters and forward-only is a feature.

A stream cipher's job is encryption with random access: seek anywhere in a file cheaply. There, being able to recompute any block (no rollback resistance) is exactly the win.

Ask yourself: is rollback resistance always desirable? (No — for a stream cipher, random access matters more, and that conflicts with rollback resistance.)

68. Break it if you can: §9.5 Different jobs want different properties

Counterexample

Discussion prompt

HMAC-DRBG is built for a generator's job: emit secrets where past outputs must stay safe even after a compromise — so rollback resistance matters and forward-only is a feature.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

A stream cipher's job is encryption with random access: seek anywhere in a file cheaply. There, being able to recompute any block (no rollback resistance) is exactly the win.

69. §9.5 ⊕ Enrichment: RC4 and ChaCha20 internals

Concept

⊕ Beyond the notes. RC4 was once the most popular stream cipher, but it is now broken: its early keystream bytes are biased (not uniformly random), which leaks information about the plaintext. Don't use it.

⊕ ChaCha20's internals are an ARX design — additions, rotations, and XORs in a 'quarter-round' that scrambles its state. The details are beyond this course; what matters is the counter-based keystream interface above.

70. Something is wrong here: 'rollback resistance is always desirable'

Anomaly

Predict first

A student writes this, and it looks reasonable:

A student: 'Rollback resistance is a security property, so a good stream cipher like ChaCha20 should have it too.'

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Misapplied goal. A stream cipher needs random access — set the counter, compute any block.

A student: which property does a stream cipher actually want?

Why: Misapplied goal. A stream cipher needs random access — set the counter, compute any block. That ability is the OPPOSITE of rollback resistance; you can't have both.

71. Trap: 'rollback resistance is always desirable'

Trap

The trap

A student: 'Rollback resistance is a security property, so a good stream cipher like ChaCha20 should have it too.'

Demand rollback resistance from a stream cipher

Why: Misapplied goal. A stream cipher needs random access — set the counter, compute any block. That ability is the OPPOSITE of rollback resistance; you can't have both.

The fix

A student: which property does a stream cipher actually want?

Favor random access; accept no rollback resistance

Why: §9.5: for AES-CTR/ChaCha20 the state is key+counter, so any block is recomputable. That's not a weakness for encryption — it's what enables seeking in a large file.

72. Which of these survive contact with L26 · HMAC-DRBG & Stream Ciphers?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
L25 told us what a secure pRNG must do. Now we build one from a tool we already trust — HMAC (L23) — and then turn its output into a working cipher.; The two values are exactly the two arguments HMAC takes. Generate them, feed them back in, and you get a fresh pseudorandom block each round.; HMAC's whole promise (L23): without the key, its output is indistinguishable from random. HMAC-DRBG just leans on that promise over and over.
Breaks
A student: 'If I pad the seed with junk like all-zeros, I'm diluting the real entropy — that must make the generator weaker.'; A student: 'Generating a new IV each time is a hassle — I'll just seed the pRNG with K and reuse the same keystream for every message.'
sound
These are stated as this lesson states them — each one survives the edge cases L26 · HMAC-DRBG & Stream Ciphers puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

73. Without one step: The HMAC-DRBG & stream-cipher playbook

Constraint

Discussion prompt

Run The HMAC-DRBG & stream-cipher playbook with this step confiscated:

Stream cipher = OTP from a pRNG: Enc(K,M) = ⟨IV, PRNG(K,IV)⊕M⟩; Dec = PRNG(K,IV)⊕C. Fresh IV per message or you get the two-time-pad leak.

Is it still possible? If it is, say what takes its place and what it costs you. If it is not, say exactly what that step was providing that nothing else does.

Hint: A step you can drop for free was never load-bearing. If you cannot drop it, name the thing that goes wrong the moment it is gone.

Answer:

  1. HMAC-DRBG state: two values K (HMAC key) and V (HMAC message); output is repeated HMAC, indistinguishable from random while the state is secret.
  2. Generate(n): loop V = HMAC(K, V), append V; then ratchet K = HMAC(K, V‖0x00), V = HMAC(K, V). Seed/Reseed fold s in via HMAC (Reseed skips the K,V=0…
  3. Good properties: absorbs long low-entropy seeds (~0.1 bits/bit ⇒ 2560 seed bits give ~256), extra/zero-entropy bytes never hurt, rollback-resistant (can't…
  4. Stream cipher = OTP from a pRNG: Enc(K,M) = ⟨IV, PRNG(K,IV)⊕M⟩; Dec = PRNG(K,IV)⊕C. Fresh IV per message or you get the two-time-pad leak.
  5. AES-CTR: a stream cipher (keystream = AES of a counter); safe only below ~2^64 bits per key, then re-key.
  6. ChaCha20: counter in the keystream ⇒ random access into huge files; trade-off is no rollback resistance (state = key+counter) — fine for a stream cipher.

74. The HMAC-DRBG & stream-cipher playbook

Pattern

  1. HMAC-DRBG state: two values K (HMAC key) and V (HMAC message); output is repeated HMAC, indistinguishable from random while the state is secret.
  2. Generate(n): loop V = HMAC(K, V), append V; then ratchet K = HMAC(K, V‖0x00), V = HMAC(K, V). Seed/Reseed fold s in via HMAC (Reseed skips the K,V=0 reset).
  3. Good properties: absorbs long low-entropy seeds (~0.1 bits/bit ⇒ 2560 seed bits give ~256), extra/zero-entropy bytes never hurt, rollback-resistant (can't invert the hash).
  4. Stream cipher = OTP from a pRNG: Enc(K,M) = ⟨IV, PRNG(K,IV)⊕M⟩; Dec = PRNG(K,IV)⊕C. Fresh IV per message or you get the two-time-pad leak.
  5. AES-CTR: a stream cipher (keystream = AES of a counter); safe only below ~2^64 bits per key, then re-key.
  6. ChaCha20: counter in the keystream ⇒ random access into huge files; trade-off is no rollback resistance (state = key+counter) — fine for a stream cipher.

75. Where does it stop working: The HMAC-DRBG & stream-cipher playbook

Edge cases

Discussion prompt

The HMAC-DRBG & stream-cipher playbook works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

  1. HMAC-DRBG state: two values K (HMAC key) and V (HMAC message); output is repeated HMAC, indistinguishable from random while the state is secret.
  2. Generate(n): loop V = HMAC(K, V), append V; then ratchet K = HMAC(K, V‖0x00), V = HMAC(K, V). Seed/Reseed fold s in via HMAC (Reseed skips the K,V=0…
  3. Good properties: absorbs long low-entropy seeds (~0.1 bits/bit ⇒ 2560 seed bits give ~256), extra/zero-entropy bytes never hurt, rollback-resistant (can't…
  4. Stream cipher = OTP from a pRNG: Enc(K,M) = ⟨IV, PRNG(K,IV)⊕M⟩; Dec = PRNG(K,IV)⊕C. Fresh IV per message or you get the two-time-pad leak.
  5. AES-CTR: a stream cipher (keystream = AES of a counter); safe only below ~2^64 bits per key, then re-key.
  6. ChaCha20: counter in the keystream ⇒ random access into huge files; trade-off is no rollback resistance (state = key+counter) — fine for a stream cipher.

76. Rule out three: Checkpoint — same keystream, two messages

Elimination

Eliminate the wrong options

What has the developer reintroduced, and what can Eve compute from C and C′ alone?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. A two-time pad: the keystream cancels, so Eve gets C ⊕ C′ = M ⊕ M′.
  • B. Nothing exploitable — a secure pRNG keystream gives perfect secrecy even when reused.
  • C. Both M and M′ in full directly, because the pRNG output is public.
  • D. Nothing, but only if the two messages are under ~2^64 bits each.

Survives elimination: A

Why: §9.5: a stream cipher is a one-time pad with a generated keystream. Reusing one keystream makes C ⊕ C′ = (ks ⊕ M) ⊕ (ks ⊕ M′) = M ⊕ M′ because ks ⊕ ks = 0. Eve recovers the XOR of the two plaintexts with no key — the L19/L21 two-time-pad break. The fix is a fresh random IV per message so PRNG(K, IV) differs each time.

77. Checkpoint — same keystream, two messages

Check

A developer seeds a secure pRNG with only the key K (no IV) and encrypts two different messages M and M′ as C = PRNG(K)⊕M and C′ = PRNG(K)⊕M′ — the SAME keystream both times. Eve intercepts C and C′ but not K. Work it out on paper first.

Check your understanding

What has the developer reintroduced, and what can Eve compute from C and C′ alone?

  • A. A two-time pad: the keystream cancels, so Eve gets C ⊕ C′ = M ⊕ M′. (correct)
  • B. Nothing exploitable — a secure pRNG keystream gives perfect secrecy even when reused.
  • C. Both M and M′ in full directly, because the pRNG output is public.
  • D. Nothing, but only if the two messages are under ~2^64 bits each.

Answer: A

Why: §9.5: a stream cipher is a one-time pad with a generated keystream. Reusing one keystream makes C ⊕ C′ = (ks ⊕ M) ⊕ (ks ⊕ M′) = M ⊕ M′ because ks ⊕ ks = 0. Eve recovers the XOR of the two plaintexts with no key — the L19/L21 two-time-pad break. The fix is a fresh random IV per message so PRNG(K, IV) differs each time.

Why B tempts people
Reusing the keystream destroys secrecy exactly as reusing a one-time pad does. A secure pRNG keystream is safe only when used ONCE per key; reuse cancels it and leaks M ⊕ M′.
Why C tempts people
Eve gets only the combined M ⊕ M′, not the individual plaintexts. Separating them needs extra information (a known message or crib-dragging) — the pRNG output is secret, not public.
Why D tempts people
The ~2^64-bit figure is the AES-CTR single-key data limit, a different issue. Keystream reuse leaks M ⊕ M′ regardless of message length — even a few bits are enough.

78. Misconceptions to retire

Concept

79. Synthesis — randomness becomes confidentiality

Concept

80. Primary sources & where to read more

Concept

81. Connect it up: L26 · HMAC-DRBG & Stream Ciphers

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — Building a pRNG: HMAC-DRBG · Why HMAC-DRBG Is Good · Encrypting a Stream: Stream Ciphers · A Block Cipher as a Stream: AES-CTR · A Counter-Native Cipher: ChaCha20. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

82. Recap — Lesson 26

Recap

You can now run HMAC-DRBG's Seed/Reseed/Generate over the (K, V) state, explain why it absorbs low-entropy seeds and is rollback-resistant, build a stream cipher as a pRNG keystream XOR-ed with the message, encrypt and decrypt with Enc(K,M)=⟨IV, PRNG(K,IV)⊕M⟩, and compare AES-CTR's data limit with ChaCha20's counter-driven random access.

Idea§The one-line version
HMAC-DRBG state9.4K = HMAC key, V = HMAC message; output is repeated HMAC
Generate9.4V = HMAC(K, V), append; then ratchet K and V forward
Good properties9.4Absorbs low-entropy seeds; extra bytes never hurt; rollback-resistant
Dual_EC_DRBG ⊕9.4Backdoored DRBG — prefer transparent, analyzable designs
Stream cipher9.5PRNG(K,IV) ⊕ M — a one-time pad with a generated pad
Fresh IV9.5Reuse the keystream ⇒ two-time pad, C⊕C′ = M⊕M′ leaks
AES-CTR9.5Stream cipher; keep below ~2^64 bits per key
ChaCha209.5Counter ⇒ random access; trade-off: no rollback resistance

Sources

  1. CS 161 Computer Security Textbook §9.4–9.5 — Wagner, Weaver, Kao, Shakir, Law & Ngai, UC Berkeley — HMAC-DRBG: a deterministic random bit generator built from HMAC, its Seed/Reseed/Generate algorithms, low-entropy seed absorption and rollback resistance (§9.4); stream ciphers as a pseudorandom one-time pad, the Enc/Dec scheme, AES-CTR as a stream cipher and ChaCha20's counter-based random access (§9.5)
  2. NIST SP 800-90A Rev. 1 — Recommendation for Random Number Generation Using Deterministic Random Bit Generators — E. Barker & J. Kelsey, NIST, June 2015 — standardizes HMAC_DRBG (and the now-withdrawn Dual_EC_DRBG)
  3. ChaCha, a variant of Salsa20 — D. J. Bernstein, 2008 — the ChaCha20 stream cipher and its ARX quarter-round design
  4. RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols — Y. Nir & A. Langley, IRTF, June 2018 — the ChaCha20 keystream with a 32-bit block counter and 96-bit nonce
  5. On the Practical Exploitability of Dual EC in TLS Implementations — S. Checkoway, M. Fredrikson, R. Niederhagen, A. Everspaugh, M. Green, T. Lange, T. Ristenpart, D. J. Bernstein, J. Maskiewicz, H. Shacham, USENIX Security 2014 — the Dual_EC_DRBG backdoor and its exploitation (⊕ supplemental, not in §9.4)

Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108