Skip to content
KEDBYTE
How Identity Works
Chapter
22

Challenge and Response

Part II · Proving It Is You|13,927 words|about 61 min read|Volume 2

22.0 What this chapter gives you#

  1. You will be able to explain to somebody with no technical background why a system that asks the same question every time can be defeated by a tape recorder, and why one that asks a different question every time cannot.
  2. You will be able to define a replay attack precisely, distinguish it from eavesdropping and from theft of a secret, and say which of the three each authentication method actually stops.
  3. You will be able to name the three sources of freshness used in real protocols, state the specific failure mode of each, and choose between them for a given system with reasons you can defend.
  4. You will be able to read a CHAP exchange byte by byte from RFC 1994, compute the response yourself with a hash function, and say exactly what CHAP requires the server to store and why that is a problem.
  5. You will be able to describe the Kerberos exchange in its own vocabulary — the key distribution centre, the ticket, the session key, the authenticator — and say why the whole design collapses if two clocks drift more than a few minutes apart.
  6. You will be able to state what a signature in a challenge-response protocol must cover, and give three real examples of protocols that get the coverage right, with the exact list of fields each of them signs.
  7. You will be able to explain why a challenge that the responder can predict is not a challenge at all, and describe a real payment system in which predictable challenges allowed cards to be cloned without ever touching the card’s keys.
  8. You will be able to define channel binding, name the two channel binding types that matter as of August 2026, say which one TLS 1.3 requires and why the older one was withdrawn.
  9. You will be able to explain what origin binding is, why it is the property that defeats a real-time phishing site, and how it differs from channel binding in what it protects against.
  10. You will be able to take a worked exchange in real bytes — a challenge, a client data structure, an authenticator data structure, a signature — and say for each field what would break if it were removed.

There is an old and very simple attack. Somebody watches you unlock a door, writes down what you did, and later does the same thing. If unlocking the door means saying a word, they say the word. If it means holding up a card, they copy the card. If it means typing a password into a screen, they type the same password. The attack has no cleverness in it at all. It requires no mathematics, no equipment beyond a notebook, and no understanding of how the lock works. It is the oldest attack on identity that exists, and for most of the history of computing it was also the most effective one.

This chapter is about the single idea that ends it. The idea is that the door should not ask a question with a fixed answer. It should ask a different question every time, chosen at the moment you arrive, and it should refuse any answer to a question it did not just ask. Then the notebook is worthless. What was written down was the answer to a question that will never be asked again. The attacker has a perfect recording of something that will never work.

That sentence — if the answer changes every time, a recording of the answer is worthless — is the whole of this chapter, and very nearly the whole of modern authentication. Every serious login system built since the late 1970s is a variation on it. The one-time code on your phone is a variation. The chip in your bank card is a variation. The corporate single sign-on that logs you into forty internal systems is a variation. The passkey is a variation. They differ in what the question is, who chooses it, what the answer is made of and what the answer is tied to, and those differences are exactly what separates a system that resists a serious attacker from one that resists only a lazy one.

The chapter before this one, “The Key Pair”, introduced the idea that a key can come in two halves, one publishable and one secret, so that a stranger can check your mark without ever holding the thing that makes it. The chapter after this one, “Passkeys and WebAuthn”, takes the machine we build here and removes the shared secret from it entirely. This chapter is the hinge between them: it is where a key stops being a mathematical object and becomes a login. We will build the machine from nothing, break it four times, and repair it each time, and by the end you will be holding real bytes from a real exchange and able to say what each of them is for.

The plain version#

The doorman with the fixed password#

Start with the simplest arrangement anybody ever built.

A club has a doorman. Members are given a password, “moonlight”, and told to say it at the door. The doorman has a list of members and their passwords. You arrive, you say “moonlight”, the doorman checks the list, you go in.

This works, in the sense that it distinguishes members from strangers who have never been told anything. It fails against a person standing three feet away with functioning ears. They hear “moonlight”. They come back on Thursday and say “moonlight”. They go in.

It fails in a second, worse way. The doorman has to hold the list, and the list contains every member’s password in readable form. A dishonest doorman, or an honest one whose notebook is stolen, hands over every member at once. The thing the club uses to recognize you is the same thing anybody would need in order to become you.

Both failures come from one property: the answer is always the same. Because it is always the same, it can be recorded and used again. Because it can be used again, it has to be kept secret by everybody who touches it, including the doorman.

Copying an answer and using it again has a name in this book. We will call it a replay: taking a message that was valid once and sending it a second time to make it valid again. Almost everything in this chapter exists to defeat replay.

The doorman who asks a different question every time#

Now change one thing, and only one thing.

The doorman no longer waits for a password. When you arrive, he says a number out loud. Any number, chosen on the spot. Say he says forty-six.

You and the club share a rule, agreed when you joined, which nobody else knows. The rule is: take the number, multiply it by three, add seven, and tell the doorman the last two digits of the result.

Forty-six times three is one hundred and thirty-eight. Add seven and you get one hundred and forty-five. The last two digits are four and five. You say “forty-five”. The doorman does the same arithmetic on his side, gets forty-five, and lets you in.

Now stand where the eavesdropper stands. They hear two things: “forty-six” and “forty-five”. They write both down. They come back on Thursday, walk up to the door, and the doorman says “eighty-one”. Their notebook says forty-five. Forty-five is wrong. Eighty-one times three is two hundred and forty-three, plus seven is two hundred and fifty, and the answer the doorman wants is fifty. The eavesdropper says forty-five and is turned away.

The recording is worthless. Not weakened, not partly useful — worthless, because it answers a question that is not being asked.

Let us run it three times so the pattern is unmistakable.

Doorman says Your working You say
46 46 x 3 + 7 = 145 45
81 81 x 3 + 7 = 250 50
907 907 x 3 + 7 = 2728 28

Three visits, three different questions, three different answers, and no answer is ever useful twice. That is the machine. Everything else in this chapter is this machine built out of better parts.

The number the doorman says has a name we will use for the rest of the book: the challenge. What you say back is the response. The rule you both know is the secret. And the reason the whole thing works is that the challenge is new each time, which we will call freshness.

Where the doorman gets his numbers#

The doorman needs a fresh number for each arrival. There are only three honest ways to get one, and each of them behaves differently when things go wrong.

The first way is to make it up at random. He has a bag of numbered tokens, he shakes the bag, he draws one, and he reads it out. Nothing about the number depends on anything. This is the strongest method, and its weakness is that he must genuinely shake the bag. If he is lazy and the tokens come out in the order they went in, the bag is not random at all and we will see shortly how badly that ends.

The second way is to count. He keeps a tally on the wall and says the next number every time: one, then two, then three. This never repeats, which is what we wanted, and it needs no bag. Its weakness is glaring: anybody can work out what the next number will be. We will come back to why that matters enormously.

The third way is to read the clock. He says the time to the nearest second. This also never repeats, and it has a lovely extra property: he does not have to remember anything at all, because the clock does the remembering. Its weakness is that the visitor’s watch and the doorman’s clock must agree, and watches drift.

Those three — a random draw, a count, a clock — are the only three sources of freshness anybody uses. They are called a nonce (a number used once), a counter, and a timestamp, and every real protocol we look at later uses one of them or a mixture.

Why the question must be a surprise#

Here is the failure that catches system designers again and again, and it is worth being very slow about.

Suppose the doorman counts. Today’s arrivals get numbers one hundred, one hundred and one, one hundred and two, and everybody can see the tally on the wall.

A thief walks up to you in the street on Tuesday. He is holding a clipboard and wearing a plausible jacket. He says: “Membership audit. What is your answer to one hundred and seventeen?” You do the arithmetic, because it is what you always do, and you tell him: one hundred and seventeen times three is three hundred and fifty-one, plus seven is three hundred and fifty-eight, so you say fifty-eight.

He writes it down. He waits until the tally on the club wall reads one hundred and sixteen. He walks up. The doorman says one hundred and seventeen. He says fifty-eight. He goes in.

Nothing was broken. The secret rule was never revealed. The doorman behaved correctly. The eavesdropping attack still does not work. But the thief got in, because he could work out in advance which question would be asked, and could get you to answer it early.

So freshness is not enough on its own. The challenge must also be a surprise — specifically, a surprise to the person answering it. If you can be persuaded to answer tomorrow’s question today, a fresh challenge protects nothing. Hold on to that, because there is a real payment system in the technical half of this chapter that was broken in exactly this way, on real cash machines, with real money.

The two things the doorman still cannot do#

Our doorman is now far better than the one who waited for “moonlight”. He is still missing two things.

The first is that he has to know the rule, because he does the same arithmetic you do in order to check your answer. So the club’s notebook now contains everybody’s rule rather than everybody’s password, and stealing it is exactly as bad as before. We removed the eavesdropper. We did not remove the dishonest insider.

The second is subtler and more important. Go back to the thief with the clipboard, but change his method. He builds a fake club across the street with a fake doorman. You arrive at the fake door. The fake doorman knows no rules — but he has a runner. The runner sprints to the real club, is given the real challenge, sprints back, and the fake doorman says that number to you. You answer it correctly, because it is a genuine challenge from the real club. The runner sprints back with your answer and gets in.

You did everything right. The challenge was fresh. The challenge was unpredictable. Your answer was correct and used once. And the thief is inside the club, because your answer was never tied to which door you were standing at.

That gap — a correct, fresh, single-use answer used at a different door from the one you thought you were at — is the most important unsolved problem in the plain version, and the last third of this chapter exists to solve it.

Where the plain version stops being true#

The arithmetic rule gives itself away#

The plain version used a rule you could do in your head: times three, add seven, keep the last two digits. That was chosen so you could check the worked example on paper, and it is a bad rule.

The honest version: any rule simple enough for a person to compute is simple enough for an eavesdropper to reverse. From two overheard pairs — 46 gives 45, and 81 gives 50 — you can solve for the multiplier and the offset in about a minute, and then you can answer any challenge forever. The response must be produced by a function that reveals nothing about the secret no matter how many input and output pairs an attacker collects. In practice that means a keyed hash function or a digital signature, both of which are designed and attacked for years precisely so that this property holds. The arithmetic in the plain version is a picture of the shape of the machine, not a specification of its parts.

Random is a claim about a machine, not a property of a number#

The plain version said the doorman shakes a bag. It made randomness sound like a thing you either do or do not do.

The honest version: a computer has no bag. It has software that produces numbers which look random, seeded from measurements of unpredictable physical events — the timing of interrupts, thermal noise in a circuit, the exact microsecond a key was pressed. If that seeding is done badly, the numbers are perfectly predictable to somebody who knows how the software works, while looking completely random to anybody who does not check. A device that boots with nothing to measure — a router freshly plugged in, a virtual machine cloned from an image, an embedded chip with no clock — is exactly the device most likely to produce a guessable challenge.

There is no way to look at a single number and tell whether it was well generated, and no test you can run on the challenge you received. The property lives in the generator, not in the output, which is why standards do not say “use a random number”, they say which generator to use. RFC 4086, “Randomness Requirements for Security”, published in June 2005 as best current practice number 106, exists entirely because this mistake kept being made.

The clock is a shared assumption, and shared assumptions break#

The plain version offered “read the clock” as one of three equal choices.

The honest version: using a timestamp means the two parties have agreed, without ever discussing it, on what time it is. That agreement is a dependency as real as a network cable, and it fails in ways that look nothing like a security problem when they happen. A laptop returned from a fortnight in a drawer with a dead battery comes back believing it is January of some year in the past. A server whose time synchronization daemon has quietly died drifts by a second a day and then, eight months later, refuses every login it receives. A container image ships with the clock frozen at build time.

Systems built on timestamps therefore have to allow a window of tolerance, and the window is a direct trade. Make it wide and you have handed an attacker that many minutes in which a captured message can be replayed. Make it narrow and you have built a system that locks out anybody whose clock is slightly wrong, which in a large organization is somebody every day. There is no setting that is right; there is only a setting that has been chosen deliberately with the trade understood.

A fresh answer is not a signed agreement#

The plain version treated the response as a proof of identity, full stop.

The honest version: a response proves that whoever produced it knew the secret and produced it after the challenge was issued. It proves nothing whatsoever about what the two parties are about to do. If the challenge is a plain random number and the response is a function of that number and the secret, then the response carries no opinion about the amount being transferred, the account being changed, the file being read or the machine you are logged into. It is an assertion that you are present, and nothing else.

That distinction is where a great many real losses come from. A user logs into a banking session with an excellent, unphishable, freshly challenged authentication, and then a piece of malware on the same machine changes the destination account number in the payment they are about to make. The authentication was perfect. It was authentication of a person, not authorization of a transaction. Systems that want the second thing have to put the details of the transaction inside the thing being signed, which is a design decision somebody has to make on purpose.

The secret is still shared, and that changes who can betray you#

The plain version quietly kept the club’s notebook.

The honest version: in every symmetric challenge-response scheme — every scheme where the two sides use the same secret — the verifier can impersonate the person it verifies. It must be able to, because checking your answer means computing your answer. Every server holding these secrets is therefore a target whose value is the entire user base, and a compromise of it is not “passwords leaked, tell people to change them” but “every account can be entered and every past transcript can be verified by the attacker”.

Worse, and this is specific to challenge-response rather than to passwords: many of these schemes require the secret in its original readable form on the server. A password can be stored as a hash, so a stolen database is a hard problem rather than an instant loss. A shared secret used to compute a challenge response usually cannot, because the server has to feed the actual secret into the same function you did. RFC 1994 says this in one flat sentence, which we will read exactly in the technical half.

The escape from this is the key pair: a secret that only one side holds, and a public half that lets the other side check without holding anything dangerous. Chapter 21 covers the mathematics of that; here we care only about the consequence, which is that the server’s copy stops being worth stealing.

The relay problem is not solved by anything in the plain version#

This is the one that matters most, so it gets stated bluntly.

The honest version: nothing in the plain version — not freshness, not unpredictability, not a perfect hash, not a key pair — prevents an attacker from standing between you and the real verifier and passing messages through in real time. The attacker does not need to understand the messages. They need only to be quick. They present themselves to you as the verifier and to the verifier as you, and every message you send is genuine, fresh and correct, and it arrives at a place you did not intend.

This has been known for a very long time and it has many names depending on the field. In the cryptographic literature it is the mafia fraud, named by Yvo Desmedt, Claude Goutier and Samy Bengio in “Special Uses and Abuses of the Fiat-Shamir Passport Protocol”, presented at CRYPTO '87 and published in the proceedings in 1988. In cars it is the relay attack that opens a vehicle whose key is in a bowl in the hallway. On the web it is real-time phishing, which chapter 18 covers in full as an attack: a page that looks like your bank, forwarding everything you type to your bank as you type it.

The fix has to change the shape of the response itself. The response must stop being an answer to a question and become an answer to a question asked in a particular place. Two mechanisms do this, they work differently, and they are the subject of the last part of the technical half: channel binding and origin binding.

The technical version#

Replay, stated exactly#

A replay attack is one in which an adversary captures a valid protocol message and re-sends it, unchanged or with permitted variation, in order to obtain an effect it already had. The adversary does not need to break any cryptography, learn any key, or understand any field. Replay is a protocol-level attack, and cryptographic strength is irrelevant to it: a message protected by a perfect cipher replays exactly as well as one sent in the clear.

NIST Special Publication 800-63B revision 4, published in July 2025 and effective from 1 August 2025, gives the definition in operational terms in its section on replay resistance: an authentication process resists replay attacks “if it is impractical to achieve a successful authentication by recording and replaying a previous authentication message”. It adds the observation that matters most: this is in addition to whatever the transport gives you, “since the output could be stolen before entry into the protected channel”. A password typed into a phishing page was captured before TLS ever saw it. It also states flatly that passwords are not replay resistant, “because the same authenticator output (i.e., the password itself) is provided for each authentication”.

That document makes replay resistance a hard requirement at authenticator assurance level 2 and level 3, and not required at level 1. That is a standard, written down, with a date, and it is the single clearest statement of why this chapter exists.

It is worth separating three attacks that get confused.

Attack What is captured What defeats it
Eavesdropping Traffic in transit Encryption of the channel
Replay A valid message Freshness in the message
Secret theft The stored secret Not holding the secret

Encryption does not defeat replay. Freshness does not defeat secret theft. Not holding the secret does not defeat a relay. Every real system needs all three defences and they are not substitutes.

Freshness: nonces, counters and timestamps#

Every anti-replay mechanism in existence works by making one party’s message depend on a value that has not been used before. There are three sources for that value, and choosing between them is one of the two or three real decisions in designing an authentication protocol.

A nonce is a value chosen at random by the verifier, sent to the claimant, and required in the response. Its virtue is that it needs no shared state and no shared clock, and it is unpredictable by construction. Its costs are that it forces a round trip — the verifier must speak first — and that the verifier must remember which nonces are outstanding, which is state, though short-lived state.

The nonce must be long enough that a value is never reused by accident. The relevant arithmetic is the birthday bound: after n draws from N possible values the chance that two coincide is approximately one minus the exponential of minus n squared over two N.

Nonce size Challenges issued Chance of a repeat
32 bits 65,536 about 39 per cent
64 bits 4.29 billion about 39 per cent
128 bits 4.29 billion under 1 in 10 to the 19
128 bits 1.1 trillion about 2 in 10 to the 15

A 32-bit challenge is exhausted by a medium-sized website in an afternoon. 128 bits is the ordinary modern answer, and it is what the W3C Web Authentication specification, WebAuthn, uses in its own examples: 32 random bytes.

A counter is a value that increases by one each time. Its virtue is that it detects replay perfectly, needs no randomness at all, and needs no clock. Its costs are that both sides must keep the count and keep it in step, that a lost message desynchronizes them, and — decisively — that a counter is predictable. A counter is a fine detector of replay and a terrible challenge.

A timestamp is the current time, sent by the claimant. Its virtue is that it removes the round trip entirely: the claimant can speak first, without waiting to be challenged, which is why it is the choice in systems that must scale to thousands of servers. Its costs are the clock dependency, the tolerance window, and the fact that within that window the message is still replayable unless the verifier separately remembers what it has seen.

Source Round trip needed Predictable State kept
Random nonce Yes No Outstanding nonces
Counter No Yes The count, both sides
Timestamp No Yes Seen list, or nothing

Real protocols mix them. Kerberos uses a timestamp plus a replay cache. The UMTS mobile authentication scheme uses a random challenge plus a sequence number. WebAuthn uses a random challenge plus an optional signature counter. Mixing is normal, and each ingredient is doing a different job.

CHAP, RFC 1994, byte by byte#

The Challenge-Handshake Authentication Protocol is the cleanest small example of the machine, and it is old enough that its specification is short. RFC 1994, “PPP Challenge Handshake Authentication Protocol (CHAP)”, by W. Simpson, was published in August 1996 and obsoletes RFC 1334. It was designed for dial-up links, and its shape is still visible in enterprise network authentication today.

The exchange has three messages and one packet format. Every CHAP packet begins with a Code of one octet, an Identifier of one octet, and a Length of two octets. Code 1 is Challenge, 2 is Response, 3 is Success, 4 is Failure. Challenge and Response packets then carry a Value-Size of one octet, a Value of that many octets, and a Name field that runs to the end of the packet.

 0                   1                   2
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Code      |  Identifier   |     Length    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Value-Size   |  Value ...
+-+-+-+-+-+-+-+-+
|  Name ...
+-+-+-+-+-+-+-+-+

The response is defined in one sentence, which is worth quoting because the order of the three items is load-bearing. The Response Value is “the one-way hash calculated over a stream of octets consisting of the Identifier, followed by (concatenated with) the ‘secret’, followed by (concatenated with) the Challenge Value”. Algorithm 5 is CHAP with MD5.

Let us run one. The server is called kedbyte-nas, the user is shikhar, and the shared secret is the twenty-one octets of the text kedbyte-shared-secret. The server picks Identifier 0x2a and a sixteen-octet challenge. Here is the challenge packet on the wire.

0000  01 2a 00 20 10 3f a2 0b 7c 91 4e 6d 08 c5 1b 33
0010  e7 a9 40 2f d6 6b 65 64 62 79 74 65 2d 6e 61 73

Reading it: 01 is Challenge. 2a is the Identifier. 0020 is the Length, thirty-two octets, which is the whole packet. 10 is the Value-Size, sixteen. The next sixteen octets are the challenge value. The remaining eleven octets, 6b 65 64 ... 61 73, are the name kedbyte-nas in plain text.

The client now computes MD5 over thirty-eight octets: the single octet 2a, then the twenty-one octets of the secret, then the sixteen octets of the challenge.

input  2a
       6b 65 64 62 79 74 65 2d 73 68 61 72 65 64 2d
       73 65 63 72 65 74
       3f a2 0b 7c 91 4e 6d 08 c5 1b 33 e7 a9 40 2f d6
MD5    49 f7 99 5c 2f 6e 05 24 81 76 f1 10 c0 1f 14 a5

And sends it back.

0000  02 2a 00 1c 10 49 f7 99 5c 2f 6e 05 24 81 76 f1
0010  10 c0 1f 14 a5 73 68 69 6b 68 61 72

02 is Response, 2a matches the Identifier of the challenge it answers, 001c is twenty-eight octets, 10 is again sixteen, then the digest, then the seven octets 73 68 69 6b 68 61 72, which spell shikhar. The server computes the same MD5 and, if it matches, replies with a Success packet: 03 2a 00 0b followed by the message text.

Two design points in that packet are easy to miss. The Identifier is inside the hash, not merely beside it, which stops a response to one challenge being reused as a response to another challenge that happened to carry the same value. And RFC 1994 states, in section 2.3, that each challenge value “SHOULD be unique” and “SHOULD also be unpredictable, least an attacker trick a peer into responding to a predicted future challenge”. That is the clipboard attack from the plain version, written into a standard in 1996.

The symmetric-secret limitation, and what MS-CHAPv2 demonstrated#

CHAP has a sentence in section 2.2 that decides its fate: “CHAP requires that the secret be available in plaintext form. Irreversably encrypted password databases commonly available cannot be used.” The spelling is the RFC’s own.

This is not an implementation weakness. It is arithmetic. The server must compute MD5 of the identifier, the secret and the challenge, so the server must hold the secret. Every user’s actual password, in readable form, in one place. A password file protected by a modern password hash is a nuisance for an attacker; a CHAP secret file is a complete compromise the moment it is copied. That is the price of symmetric challenge-response, and it is paid by every scheme in the family.

There is a second limitation, which is that a symmetric scheme cannot prove anything to you about the server. In plain CHAP the server challenges the client and the client answers. A fake server can issue challenges all day, collect responses, and learn nothing directly — but it also has to be nobody in particular, since the client never checks who it is talking to.

Microsoft’s answer to the second problem was MS-CHAPv2, specified in RFC 2759, “Microsoft PPP CHAP Extensions, Version 2”, by G. Zorn, published as an informational document in January 2000. It adds mutual authentication: the client contributes its own sixteen-octet Peer-Challenge, and the server must return a value proving it knew the secret too. The Response field is forty-nine octets: sixteen of Peer-Challenge, eight reserved and set to zero, twenty-four of NT-Response, and one flags octet. The NT-Response is built by taking an eight-octet challenge hash — SHA-1 over the peer challenge, the authenticator challenge and the user name, truncated to eight octets — and encrypting it three times under three DES keys derived by zero-padding the sixteen-octet password hash out to twenty-one octets and cutting it into three seven-octet pieces.

At DEF CON 20 in July 2012, Moxie Marlinspike and David Hulton showed that because the third DES key is derived from only two remaining bytes of the hash padded with zeroes, the entire protocol reduces to the strength of a single DES encryption — a search of 2 to the power 56. Their tool chapcrack extracted the parameters from a captured handshake and a rented field-programmable gate array service returned the key. Marlinspike’s own summary was that “the security of MS-CHAPv2 can be reduced to the strength of a single DES encryption”. Microsoft published a security advisory in August 2012 recommending that MS-CHAPv2 not be used without a protecting tunnel.

The lesson is not that DES is old. It is that a challenge-response protocol can be perfectly correct at the protocol level — fresh challenge, mutual authentication, no replay — and still be broken, because the strength of the whole thing is the strength of the weakest function inside the response.

The same shape, better packaging: HTTP Digest and SCRAM#

Two other symmetric schemes are worth naming because they show how far the family can be pushed.

HTTP Digest Access Authentication, most recently specified in RFC 7616 by Shekh-Yusef, Ahrens and Bremer in September 2015, obsoleting RFC 2617, is challenge-response carried in HTTP headers. The server sends a WWW-Authenticate header carrying, among other parameters, realm, nonce, opaque, algorithm and qop. The client replies with an Authorization header carrying username, realm, uri, nonce, nc, cnonce, qop and response. With qop set to auth, A1 is the user name, the realm and the password joined by colons, A2 is the method and the request URI joined by a colon, and the response is a keyed digest of H(A1) against the nonce, the nonce count, the client nonce, the qop value and H(A2).

Three features of that formula matter. The nonce count, nc, lets the server detect a replay of a response it has already seen against the same nonce. The client nonce, cnonce, means the client also contributes freshness, so a malicious server cannot choose the entire input and precompute a dictionary against it. And A2 contains the method and the URI, so the response is bound to the specific request — the earliest widely deployed example of the idea that a response should cover what it is authorizing, not merely who is present. RFC 7616 also suggests constructing the nonce from a timestamp and a hash of that timestamp with server-private data, which lets the server verify a nonce is its own and is recent without storing a list.

The Salted Challenge Response Authentication Mechanism, SCRAM, specified in RFC 5802 by Newman, Menon-Sen, Melnikov and Williams in July 2010, goes further and removes the plaintext-secret problem. It is a four-message exchange. The client sends a user name and a client nonce, marked n= and r=. The server replies with the nonce extended by its own random part, a salt s= and an iteration count i=. The client sends back the channel binding data c=, the full nonce r= and a proof p=. The server closes with a signature v=.

SaltedPassword  := Hi(Normalize(password), salt, i)
ClientKey       := HMAC(SaltedPassword, "Client Key")
StoredKey       := H(ClientKey)
AuthMessage     := client-first-message-bare + "," +
                   server-first-message + "," +
                   client-final-message-without-proof
ClientSignature := HMAC(StoredKey, AuthMessage)
ClientProof     := ClientKey XOR ClientSignature
ServerSignature := HMAC(ServerKey, AuthMessage)

The server stores only StoredKey and ServerKey, not the password and not ClientKey. It verifies a proof by computing ClientSignature, exclusive-ORing it with the proof to recover ClientKey, hashing that, and comparing with StoredKey. So a stolen SCRAM database does not immediately yield passwords, and the iteration count makes brute-forcing them expensive. AuthMessage covers every message of the exchange, both nonces included, which means the proof is not a response to a number but a response to the entire conversation so far. That idea — sign the transcript, not the challenge — is the one the rest of this chapter keeps returning to.

Kerberos: the KDC, the ticket, the authenticator and the clock#

Kerberos is the most consequential challenge-response system ever built, in the sense that more logins happen under it every morning than under anything else. It is the authentication mechanism inside Microsoft Active Directory, which means it is what happens when most of the world’s office workers sit down at a desk.

Its history is specific. Project Athena was launched at the Massachusetts Institute of Technology in 1983. Kerberos came out of it: versions 1 to 4 were the work of Steve Miller and Clifford Neuman with Jerome Saltzer and Jeffrey Schiller, and the public description is Steiner, Neuman and Schiller, “Kerberos: An Authentication Service for Open Network Systems”, presented at the USENIX conference in Dallas in February 1988. Version 5 was specified in RFC 1510 by Kohl and Neuman in September 1993, and re-specified, with many clarifications, in RFC 4120, “The Kerberos Network Authentication Service (V5)”, by Neuman, Yu, Hartman and Raeburn, in July 2005. RFC 4120 obsoletes RFC 1510 and is the current base specification as of August 2026. RFC 1510 says plainly that “the Kerberos model is based in part on Needham and Schroeder’s trusted third-party authentication protocol and on modifications suggested by Denning and Sacco”.

The architecture has three parties, not two. There is you, there is the service you want, and there is a trusted third party called the key distribution centre, or KDC, which holds a long-term secret key for every principal in the realm — every user and every service. The KDC has two halves: the authentication service, which you talk to once at the start of the day, and the ticket-granting service, which you talk to each time you want a new service. The flow is six messages in three pairs.

   you                KDC: AS      KDC: TGS        service
    |                    |            |               |
    |-- AS-REQ (10) ---->|            |               |
    |<- AS-REP (11) -----|            |               |
    |   (TGT + session key)           |               |
    |                                 |               |
    |-- TGS-REQ (12) ---------------->|               |
    |<- TGS-REP (13) -----------------|               |
    |   (service ticket + new key)    |               |
    |                                 |               |
    |-- AP-REQ (14) ----------------------------->    |
    |<- AP-REP (15), if mutual required ---------     |

A ticket is a small structure the KDC issues, encrypted under the long-term key of the service it names, which you cannot read and cannot alter. In ASN.1 it is tagged APPLICATION 1 and carries tkt-vno, realm, sname and an encrypted part. That encrypted part, EncTicketPart, carries flags, the session key, the client’s realm and name, transited-realm information, and then a run of times: authtime, an optional starttime, endtime, an optional renew-till, optional client addresses and optional authorization data. The ticket is a statement by the KDC to the service: the holder of the enclosed session key is this client, and this statement is valid between these times.

The ticket alone proves nothing about you, because a ticket is just bytes and bytes can be copied. What proves you are present is the authenticator, and here is its exact definition from RFC 4120.

Authenticator ::= [APPLICATION 2] SEQUENCE {
    authenticator-vno  [0] INTEGER (5),
    crealm             [1] Realm,
    cname              [2] PrincipalName,
    cksum              [3] Checksum OPTIONAL,
    cusec              [4] Microseconds,
    ctime              [5] KerberosTime,
    subkey             [6] EncryptionKey OPTIONAL,
    seq-number         [7] UInt32 OPTIONAL,
    authorization-data [8] AuthorizationData OPTIONAL
}

The authenticator is encrypted under the session key that is inside the ticket. Only the KDC, you and the service ever hold that key. The service decrypts the ticket with its own long-term key, pulls out the session key, and uses it to decrypt the authenticator. If that works, whoever sent the authenticator had the session key, which means they received the ticket legitimately.

Notice what the authenticator does not contain: a challenge. There is no random value from the service in it at all. The freshness comes from ctime and cusec — the client’s clock, to the second and the microsecond. This is the design decision that gives Kerberos its scale, because the client can authenticate to a service without a prior round trip, and it is also the decision that makes clocks load-bearing.

RFC 4120 handles the consequences in section 3.2.3, and the wording is worth having exactly. On skew: “If the local (server) time and the client time in the authenticator differ by more than the allowable clock skew (e.g., 5 minutes), the KRB_AP_ERR_SKEW error is returned.” On replay: “Unless the application server provides its own suitable means to protect against replay ... the server MUST utilize a replay cache to remember any authenticator presented within the allowable clock skew ... The replay cache will store at least the server name, along with the client name, time, and microsecond fields from the recently-seen authenticators, and if a matching tuple is found, the KRB_AP_ERR_REPEAT error is returned.”

Read those two together and the design is clear. The timestamp narrows the replay window to the skew tolerance. The replay cache closes what is left of it. Neither alone is sufficient, and RFC 4120 says so: a server that loses its cache “MUST reject all requests until the clock skew interval has passed”, because otherwise an attacker who recorded a ticket and authenticator could replay them the instant the server forgot.

Section 1.6 notes that the looseness “can be configured on a per-server basis, but it is typically on the order of 5 minutes”. Five minutes is a convention rather than a constant in the protocol, but a convention so universal that it is effectively a constant. A machine whose clock has drifted further than that cannot log in at all, and the error it gives is about time rather than about passwords, which is why “check the clock” is the first thing an experienced administrator does with a Kerberos failure.

Pre-authentication uses the same clock. The PA-ENC-TIMESTAMP pre-authentication data is an encrypted PA-ENC-TS-ENC structure containing patimestamp and an optional pausec. It exists so that the KDC will not hand out a ticket encrypted under a user’s password-derived key to anybody who merely asks, which would give an attacker material for offline password guessing. It works, and it makes the clock dependency start at the first message of the day.

Kerberos element What it proves Freshness source
Ticket KDC vouched for you Ticket lifetime
Authenticator You hold the session key Client clock
Replay cache This is not a repeat Server memory
AP-REP The service is genuine Echoed ctime

Bellovin and Merritt set out the honest criticisms early, in “Limitations of the Kerberos Authentication System”, published in Computer Communications Review volume 20 number 5 in October 1990 and presented at the Winter 1991 USENIX conference in Dallas. Their points included this dependency on secure clocks, the offline-guessing exposure of the version 4 exchange, and the difficulty of protecting the replay cache. Pre-authentication and the version 5 redesign answered some of it. The clock dependency was never removed, because it is not a bug; it is the price of the round trip that Kerberos does not have to make.

Needham-Schroeder, Denning-Sacco, and the Lowe attack#

Three papers, seventeen years apart, contain most of what is known about how these protocols fail.

Roger Needham and Michael Schroeder published “Using encryption for authentication in large networks of computers” in Communications of the ACM, volume 21 number 12, in December 1978, pages 993 to 999. It is the origin of the trusted-third-party pattern that Kerberos implements, and it contains the first clear statement that a nonce is doing a specific job: proving that a message is a reply to this run of the protocol and not a recording of an earlier one.

Dorothy Denning and Giovanni Maria Sacco published “Timestamps in key distribution protocols” in Communications of the ACM, volume 24 number 8, in August 1981, pages 533 to 535. They showed that the symmetric Needham-Schroeder protocol has a replay weakness: if an old session key is ever compromised, an attacker can replay the message that delivers it, and the recipient cannot tell the key is stale because nothing in the message says when it was made. Their remedy was to put a timestamp in, and that remedy is the direct ancestor of the Kerberos authenticator. The line runs from a 1981 attack paper straight into a login you performed this morning.

Gavin Lowe published “An attack on the Needham-Schroeder public-key authentication protocol” in Information Processing Letters, volume 56 number 3, in November 1995, pages 131 to 136, and followed it with “Breaking and Fixing the Needham-Schroeder Public-Key Protocol Using FDR” at TACAS in 1996, pages 147 to 166. The second title names the tool: FDR is a model checker for the process algebra CSP, and Lowe found the attack by mechanical exploration of the protocol’s state space rather than by inspection. Machine-checking protocols instead of arguing about them is now standard practice, and it began here.

The attack itself is short enough to set out in full. The public-key protocol has three messages, where curly braces mean encryption under the named party’s public key and N is a nonce.

1.  A -> B :  { N_A , A } pk(B)
2.  B -> A :  { N_A , N_B } pk(A)
3.  A -> B :  { N_B } pk(B)

Alice sends her nonce and her name to Bob. Bob returns her nonce and adds his own. Alice returns Bob’s nonce. Each has seen the other decrypt something only they could decrypt, so each believes the other is present. Now let an intruder I be somebody Alice is willing to talk to — a service she uses, not necessarily an obvious villain.

1.   A -> I    :  { N_A , A } pk(I)
1'.  I(A) -> B :  { N_A , A } pk(B)
2'.  B -> I(A) :  { N_A , N_B } pk(A)
2.   I -> A    :  { N_A , N_B } pk(A)
3.   A -> I    :  { N_B } pk(I)
3'.  I(A) -> B :  { N_B } pk(B)

Alice starts a legitimate session with I. I opens a session with Bob, pretending to be Alice, using Alice’s own nonce. Bob’s reply is encrypted to Alice, so I cannot read it, and does not need to: I forwards it unchanged. Alice, seeing her own nonce come back, believes she is talking to I and returns N_B encrypted to I. I decrypts it, re-encrypts it to Bob, and Bob concludes that Alice has authenticated to him. She has not. She was never talking to Bob at all.

Every message was fresh. Every nonce was unpredictable. No cryptography was broken. The protocol failed because message 2 did not say who sent it. Lowe’s fix is one field: message 2 becomes the triple containing N_A, N_B and B’s identity, at which point Alice can see that the responder is Bob and not I, and the attack disappears. The corrected version is called Needham-Schroeder-Lowe.

The general lesson is the one that governs the whole of the rest of this chapter. A response must cover not only the challenge but the context: who is speaking, to whom, over what connection, for what purpose. Anything the response fails to cover is something the attacker is free to change.

Signature-based challenge-response, and what the signature must cover#

Everything so far has been symmetric: both sides hold the same secret. Replace the secret with a key pair — a private half that never leaves you and a public half the verifier holds, which chapter 21 explains — and three things change at once.

The verifier no longer holds anything that would let it impersonate you, so a breach of its database is embarrassing rather than catastrophic. The verifier can be a stranger, because it needs only your public half. And the response becomes a signature, which is a mathematical statement about a specific block of bytes rather than a value derived from a secret.

That last point is where the design work is. A signature says: the holder of this private key signed exactly these bytes. It says nothing at all about bytes that were not included. So the question “what does the signature cover” is not a detail; it is the entire security of the protocol. Every field left out is a field an attacker may change freely.

Three real protocols answer the question, and their answers are instructive because they are so explicit.

SSH public-key authentication, RFC 4252, specifies the signed data as an ordered list.

string    session identifier
byte      SSH_MSG_USERAUTH_REQUEST
string    user name
string    service name
string    "publickey"
boolean   TRUE
string    public key algorithm name
string    public key to be used for authentication

The first item is the session identifier, which is derived from the SSH key exchange and is unique to this particular connection. That single field is what stops a captured SSH authentication being replayed on another connection: the signature is bound to the transport underneath it. The rest names the user, the service and the key, so none of those can be substituted.

TLS 1.3, RFC 8446, defines its CertificateVerify signature in section 4.4.3, and the construction is unusually careful. The content signed is the concatenation of four things: sixty-four octets each equal to 0x20, then a context string, then a single zero octet as a separator, then the transcript hash of the whole handshake so far. The context string is “TLS 1.3, server CertificateVerify” for a server and “TLS 1.3, client CertificateVerify” for a client. The specification gives a worked example: with a transcript hash of thirty-two octets each equal to 0x01, the bytes signed are

2020202020202020202020202020202020202020202020202020202020202020
2020202020202020202020202020202020202020202020202020202020202020
544c5320312e332c207365727665722043657274696669636174655665726966
79
00
0101010101010101010101010101010101010101010101010101010101010101

Every element there is doing a job. The sixty-four spaces exist so that a signature made in TLS 1.3 can never be confused with a signature over a message from an older protocol version that did not have the prefix. The context string separates client signatures from server signatures, so a client’s signature cannot be replayed as a server’s, and RFC 8446 says so directly: it “is used to provide separation between signatures made in different contexts, helping against potential cross-protocol attacks”. The zero byte prevents the context string running into the content. And the transcript hash means the signature covers every byte of the handshake — including both random values, both parties’ identities and the negotiated parameters — so nothing in the handshake can be altered without invalidating it.

WebAuthn, the browser standard for signature-based authentication, defines the signed data as the binary concatenation of the authenticator data and the client data hash. We will lay those out in bytes shortly.

The pattern in all three is the same, and it is the design rule to take away.

Must be covered Prevents
The challenge or transcript Replay of an old response
The connection identity Use on a different channel
The verifier’s identity Use at a different verifier
A context or role string Reuse in a different role

The challenge must be unpredictable: EMV and the pre-play attack#

RFC 1994 asked for unpredictable challenges in 1996. Here is what happens when the request is ignored, on a scale of millions of terminals.

EMV — the chip-and-PIN system used on payment cards worldwide — is a challenge-response protocol. When you put a card in a terminal, the terminal generates a nonce that the specification calls the unpredictable number, four octets long, and sends it to the card along with the transaction details. The card computes an application cryptogram over the transaction data and the unpredictable number using a key it shares with the issuing bank, and returns it. The bank checks the cryptogram. The unpredictable number is the entire freshness of the transaction: it is what stops an attacker who once saw a valid cryptogram from presenting it again.

In September 2012, Mike Bond, Omar Choudary, Steven J. Murdoch, Sergei Skorobogatov and Ross Anderson of the University of Cambridge published “Chip and Skim: cloning EMV cards with the pre-play attack”, later presented at the IEEE Symposium on Security and Privacy in 2014. Their finding, in their own words: “We have discovered that some EMV implementers have merely used counters, timestamps or home-grown algorithms to supply this number.”

Consider what that permits. If the unpredictable number is a counter, or a function of the clock, or the output of a poorly seeded generator, then an attacker who can work out what a particular cash machine will ask at 14:32 tomorrow can obtain, today, the cryptogram that answers it. They obtain it by putting a modified terminal in front of a genuine card — a shop terminal they control, or a compromised one — and asking the card for cryptograms against the numbers the target machine is going to use. The card answers honestly. Nothing in the card’s world tells it what a real terminal’s random number looks like.

The stored answers are then played to the real machine at the right moment. The paper’s central observation is the damning one: the result “is indistinguishable from card cloning from the standpoint of the logs available to the card-issuing bank, and can be carried out even if it is impossible to clone a card physically”. The bank sees a valid cryptogram from a valid card with a valid unpredictable number. The customer, disputing the transaction, is told that chip cards cannot be cloned and must therefore be mistaken or complicit. The authors say precisely that: their work “can now explain at least some of the increasing number of frauds in which victims are refused refunds by banks”.

The engineering failure is worth stating in one line: an unpredictable number that the responder can predict is not a challenge, it is a label. The cryptography was correct. The key management was correct. Four bytes generated the wrong way removed all of it.

Two further points are honest to make. Four octets is only thirty-two bits, which as our birthday table showed is not much even when generated perfectly. And the attack was found not by breaking anything but by looking at what real terminals actually emitted, which is a reminder that a specification saying “unpredictable” without saying how to test it will be implemented by somebody who has not thought about it.

Channel binding: RFC 5929, RFC 9266 and the TLS exporter#

We now return to the relay, which is the last unsolved problem in the plain version. The attacker sits between you and the verifier, forwards a genuine challenge to you and a genuine response back, and is admitted.

Channel binding solves it by making the response depend on the connection it travels over. If the response covers a value that is unique to your TLS connection, then a relay is holding a response computed for a connection it is not on. It can forward the bytes, but the verifier computes the value for its own connection — the one the attacker is using — and gets a different value, and the check fails.

A channel binding type is defined in a specification, registered with IANA, and referred to by a short string. RFC 5929, “Channel Bindings for TLS”, by J. Altman, N. Williams and L. Zhu, published in July 2010, defined three of them.

The first is tls-unique, in section 3: the bytes of the first TLS Finished message sent in the most recent handshake. In a full handshake that is the client’s, in an abbreviated one the server’s, and if renegotiation has occurred it is the innermost handshake’s. It was the type SCRAM was designed around.

The second is tls-server-end-point, in section 4: a hash of the server’s certificate as it appeared in the Certificate message. The hash is chosen by the certificate’s own signature algorithm, with SHA-256 substituted if that algorithm uses MD5 or SHA-1, and the binding is left undefined if the signature algorithm uses no hash or several. This one binds to the server’s identity rather than to the connection, which makes it survive TLS terminators and load balancers in a way tls-unique does not.

The third is tls-unique-for-telnet, in section 5, which exists for a specific Telnet profile and can be left aside.

tls-unique was then withdrawn as a general answer, and the reason is instructive. The triple handshake attack, published by Bhargavan, Delignat-Lavaud, Fournet, Pironti and Strub at the IEEE Symposium on Security and Privacy in 2014, showed that without the extended master secret extension — later standardized as RFC 7627 — an attacker could arrange for two different TLS connections to have the same master secret, and therefore the same Finished messages, and therefore the same tls-unique value. A channel binding whose value is not unique to the channel binds nothing. TLS 1.3 removed the renegotiation machinery that made this possible, but as RFC 9266 notes, “the safety of channel bindings with TLS 1.3 was not analyzed as part of the core protocol work”.

RFC 9266, “Channel Bindings for TLS 1.3”, by S. Whited, published in July 2022, defines the replacement and updates RFCs 5801, 5802, 5929 and 7677. The new type is tls-exporter, and it is defined in terms of the TLS exporter interface, which is TLS 1.3’s own mechanism for deriving key material for use outside the handshake, specified in RFC 8446 section 7.5.

TLS-Exporter(label, context_value, key_length) =
    HKDF-Expand-Label(
        Derive-Secret(Secret, label, ""),
        "exporter",
        Hash(context_value),
        key_length)

For channel binding the parameters are fixed: the label is the ASCII string “EXPORTER-Channel-Binding” with no terminating NUL, the context value is the zero-length string, and the length is 32 bytes. Both endpoints compute the same 32 bytes from their own view of the connection, and any two distinct connections produce different values.

Here is the mechanism working, with real bytes. Suppose the exporter value for the genuine connection is

40407b72dde930b5457b3a0d3c58098f
bfed5685aec21289d5b83c4cb0a9f382

and the response is an HMAC-SHA-256 over the exporter followed by the sixteen-octet challenge from our CHAP example, under a key derived from the shared secret. The bound response is

75167ae8375c18421a66d389cedad3d9
2442c358c6f41c67a8b71346baafbc4f

An attacker relaying that response is sitting on a different TLS connection, whose exporter value is a different 32 bytes, say

2cbebcc8fdaf42c3b629a995e0a1c79b
5737246f4ea574751b69130719ba94ba

The verifier, checking the relayed response against its own connection, computes

7c4bc115a3091a1d87b16bfb7f1b3b15
c53051c843a1a1c363a0a8ff641f1e9d

which does not match what arrived. The relay fails. Without channel binding the response would have been an HMAC over the challenge alone, the same on any connection, and the relay would have succeeded.

SCRAM is where channel binding is most visible in a deployed protocol. Its mechanism names carry a -PLUS suffix — SCRAM-SHA-256-PLUS, defined with SCRAM-SHA-256 in RFC 7677 — when channel binding is in use. The client’s first message begins with a flag: n means the client does not support channel binding, y means it supports it but believes the server does not, and p means it requires it, followed by the type name. The negotiation is protected because the flag is echoed inside the c= attribute, which is itself inside AuthMessage, which is inside the proof. An attacker who strips the -PLUS mechanism from the server’s advertised list to force a downgrade is detected, because the client sent y and the server knows it did offer the PLUS variant.

NIST SP 800-63B revision 4 recognizes channel binding as one of exactly two ways to achieve phishing resistance, and its wording is precise: the protocol “SHALL then strongly and irreversibly bind a channel identifier negotiated in establishing the authenticated protected channel to the authenticator output (e.g., by signing the two values together using a private key controlled by the claimant for which the public key is known to the verifier)”. It names client-authenticated TLS as the example, and notes that United States government PIV and CAC smartcards obtain phishing resistance this way.

Origin binding in the browser#

Channel binding has a practical problem in the ordinary web. It requires the thing computing the response to see the TLS connection. A browser terminates TLS; a security key plugged into a USB port does not. Passing the exporter value out to the authenticator is possible in principle and awkward in practice, and it breaks in every deployment where TLS is terminated at a load balancer and re-established behind it, which is most large deployments.

Origin binding takes a different route to the same place. Instead of binding the response to the connection, bind it to the name of the site. The browser knows, with certainty, which origin the page came from — that is the foundation of the whole web security model — and the browser is trusted to report it honestly to the authenticator. The authenticator then includes that origin in what it signs, or uses it to select which key to use, or both.

NIST SP 800-63B revision 4 calls this verifier name binding and treats it as the second of the two acceptable routes to phishing resistance: the protocol “SHALL then generate an authenticator output that is cryptographically bound to a verifier identifier that is authenticated as part of the protocol. In the case of DNS identifiers, the verifier identifier SHALL be either the authenticated hostname of the verifier or a parent domain that is at least one level below the public suffix associated with that hostname.” It names WebAuthn as the example, describing it as providing phishing resistance “by choosing an authenticator secret based on the authenticated domain name of the verifier”.

The document also compares the two honestly, and the comparison is worth having: “Channel binding is considered more secure than verifier name binding because it is not vulnerable to the misissuance or misappropriation of verifier certificates, but both methods satisfy the requirements for phishing resistance.” That is the trade in one sentence. Channel binding does not care whether an attacker has fraudulently obtained a certificate for the real site’s name, because the connection is still a different connection. Origin binding does care, because a fraudulent certificate for the real name lets an attacker present the real origin.

Here is why origin binding stops the attack that channel binding was invented for. A real-time phishing site — chapter 18 covers the attack itself, the tooling and the incidents — works by relaying. Its whole method is to be a perfect proxy: it shows you the real site’s page, takes what you type, and passes it to the real site immediately. Against a one-time code it succeeds completely, because the code means the same thing wherever it is typed.

Against origin binding it fails at the first step, for a reason it cannot engineer around. The victim’s browser is at the attacker’s site, and the attacker’s site has a different name. It must have a different name, because if it had the real name it would need the real name’s certificate, which is a different and much harder attack against the certificate system rather than against the user. The browser therefore reports the attacker’s origin to the authenticator, honestly and unavoidably, and the signature covers that origin. The real site, checking the assertion, sees an origin that is not its own and rejects it. The attacker cannot edit the origin, because it is inside the signature, and cannot ask the authenticator for a differently-signed version, because the authenticator will not sign an origin the browser did not give it.

The result is a property that no amount of user attention or user error changes. A victim who is entirely fooled, who believes the fake site completely and tries hard to log in, still cannot produce a credential the real site will accept. That is a very unusual security property, and it is the reason the industry moved. Chapter 23 covers the full passkey ceremony; what matters here is the single mechanism, which is that the name of the place is inside the thing that is signed.

The worked exchange, end to end, in bytes#

Now put the pieces together on one login and read every field. This is a signature-based challenge-response with origin binding, in the shape WebAuthn defines. The values below are real: they were computed with an Ed25519 key generated from a fixed seed, and anybody with the seed can reproduce every byte.

The private key is the thirty-two byte seed

0d141b222930373e454c535a61686f76
7d848b9299a0a7aeb5bcc3cad1d8dfe6

which yields the thirty-two byte public key

9c7aba247d4b9a8b79b56eab182574e5
f8cd9540289a1f895a069e005b58b3ca

Step one. The server generates a challenge of thirty-two random bytes and sends it to the browser.

3fa20b7c914e6d08c51b33e7a9402fd6
8b1c5e77042aa9b3ce60418df25a9c7e

Step two. The browser builds a structure recording what happened, called the client data. It contains the type of operation, the challenge in base64url form, and — the critical field — the origin the page actually came from, which the browser fills in and the page cannot influence.

{"type":"webauthn.get",
 "challenge":"P6ILfJFObQjFGzPnqUAv1oscXncEKqmzzmBBjfJanH4",
 "origin":"https://bank.example.com",
 "crossOrigin":false}

Written as a single line with no spaces, that is 137 bytes. Its SHA-256 hash is

08c25cb3544cc979efe51f8e8f7322b2
e3c126e3b3944598dc9fa3e8d1afff9c

Step three. The authenticator builds its own structure, the authenticator data, which is thirty-seven bytes when no new credential is being created.

99bd6361e925077ee4bdac273cb650bc  rpIdHash    (32)
603e6a014403bb2f97fceb98b035043c
05                                flags        (1)
0000002a                          signCount    (4)

The rpIdHash is the SHA-256 of the relying party identifier, which here is the string bank.example.com. The flags octet 0x05 is binary 00000101: bit 0 set means user present, and bit 2 set means user verified, so a human touched the device and proved themselves to it locally. Bits 3 and 4 carry backup eligibility and backup state, bit 6 signals that attested credential data follows, and bit 7 signals extensions; all are clear here. The signCount is a four-octet big-endian counter, here 42, which the authenticator increments on each use and the server may check for regression as a cloning signal.

Step four. The signature is computed over the concatenation of the authenticator data and the client data hash: 37 plus 32, sixty-nine bytes in total, in that order.

99bd6361e925077ee4bdac273cb650bc
603e6a014403bb2f97fceb98b035043c
05
0000002a
08c25cb3544cc979efe51f8e8f7322b2
e3c126e3b3944598dc9fa3e8d1afff9c

The Ed25519 signature over those sixty-nine bytes is sixty-four bytes.

fb106a4b91e45ece667a8dd8d7762525
371b8cc943fb9077ecac95d243453c44
cd87b33a9ea161f27d32befc04447bc8
159687a0fc009a1c44d22cd37948e90d

Now the same login against a phishing site. Everything is identical except the origin, which is bank-example-login.com rather than bank.example.com. The client data becomes 143 bytes and its hash becomes

debff47d550c98f41fea6a85c38d6631
e27257d67127a489ec7cbf3e0cdeb38d

The rpIdHash also changes, because the relying party identifier is different.

5a32fd4f73d2c3f6263b7c2e60fba6ee
97b5dbc8ee9775abd237e5c2a4260d49

And the signature, over the same private key, the same challenge and the same counter, is completely different.

6ab1ae8339b4e74d80dea7b9eb443671
401c1aecbae2e1025180b88bfa6924a0
ec5e6eb6f251bf90f0022b5eb98b6386
a4c4f1634f1b573d8adfec6a9f7da706

The real server, receiving that, does two checks that both fail: it hashes its own relying party identifier and compares with the rpIdHash, and it parses the client data and compares the origin against its expected value. There is nothing the attacker can do with those bytes. They are a valid signature by the right key over the right challenge, and they are useless, because they say out loud where they were made.

That is the whole chapter in one example. The challenge stops the recording being replayed. The signature stops the response being forged. The origin stops the response being relayed. Take away any one and a specific, well-documented, widely used attack becomes available again.

Field removed Attack unlocked
Challenge Replay of an old login
Signature counter Undetected cloned authenticator
Origin in client data Real-time phishing relay
rpIdHash Cross-site credential reuse
User presence flag Silent use without the human

A checklist for designing one of these#

If you ever specify a challenge-response exchange, these are the questions that separate a correct one from one that will appear in somebody else’s paper.

  1. Who generates the challenge, and from what generator. Name the generator, not the language function. If either party can predict it, it is a label, not a challenge.
  2. How many bits is it. Compute the birthday bound against your expected volume over the system’s whole life, not this year’s.
  3. What exactly does the response cover. Write the byte layout out in order, as SSH and TLS 1.3 do, and defend every omission.
  4. Is the verifier’s identity inside the signed data. If not, read Lowe 1995 again.
  5. Is the channel or the origin inside the signed data. If neither, a relay works and no amount of freshness will stop it.
  6. What does the verifier store, and what happens if that store is copied. If the answer is “every user’s secret in the clear”, say so out loud to whoever is accepting the risk.
  7. If you used a timestamp, what is the tolerance, who monitors clock drift, and what is the replay cache. Kerberos needs all three; so do you.
  8. If you used a counter, what happens when it desynchronizes, and how does a legitimate user recover.
  9. Is the response bound to the transaction, or only to the session. Those are different products.
  10. Can the exchange be downgraded to a weaker variant, and is that negotiation itself covered by the response, as SCRAM’s channel binding flag is.

22.98 Common wrong ideas#

Wrong: TLS already encrypts everything, so replay is not a concern. Right: Encryption stops an eavesdropper reading traffic; it does nothing about a message captured before it entered the channel or a message replayed by a party who is legitimately inside the channel. NIST SP 800-63B revision 4 makes the point explicitly in its replay resistance section: the requirement is “in addition to the replay-resistant nature of authenticated protected channel protocols, since the output could be stolen before entry into the protected channel”. A one-time code typed into a phishing page never travelled over the victim’s TLS connection to the real site at all.

Wrong: A challenge-response protocol proves who you are. Right: It proves that whoever produced the response held the secret or key at that moment, and — if the protocol is built correctly — that they did so after the challenge was issued. It says nothing about who that person is, whether they consented, what they are authorizing, or whether the key was stolen last Tuesday. Binding a name to a key is a separate mechanism handled by registration and certificates, and binding a transaction to a response requires putting the transaction inside the signed data on purpose.

Wrong: Any random-looking value works as a challenge, so a counter or a timestamp will do. Right: A counter and a timestamp both give freshness and neither gives unpredictability, and unpredictability is a separate requirement. RFC 1994 asked for both in 1996: each challenge “SHOULD be unique” and “SHOULD also be unpredictable”. The Cambridge pre-play work of September 2012 found EMV implementers who had used “counters, timestamps or home-grown algorithms” for the four-byte unpredictable number, which allowed cryptograms to be harvested in advance and produced fraud indistinguishable from card cloning in the bank’s own logs.

Wrong: CHAP is secure because the password never crosses the wire. Right: The password never crosses the wire and must sit in readable form on the server, which is the worse exposure of the two. RFC 1994 section 2.2 states it: “CHAP requires that the secret be available in plaintext form. Irreversably encrypted password databases commonly available cannot be used.” Every symmetric challenge-response scheme has some version of this problem; SCRAM reduces it by storing only derived values, and key-pair schemes remove it by giving the server nothing worth stealing.

Wrong: MS-CHAPv2 fixed CHAP’s problems because it adds mutual authentication. Right: It fixed one problem and left the cryptography weak. At DEF CON 20 in July 2012 Moxie Marlinspike and David Hulton showed that the three-DES-key construction over the padded MD4 password hash reduces the whole protocol to the strength of a single DES encryption, roughly 2 to the power 56, and demonstrated recovery of the key from a captured handshake with a rented hardware service. Microsoft published a security advisory in August 2012 saying it should not be used without a protecting tunnel.

Wrong: Kerberos does not need clocks because it uses tickets. Right: Kerberos deliberately has no challenge from the service; the freshness in the authenticator comes entirely from the client’s ctime and cusec fields. RFC 4120 section 3.2.3 returns KRB_AP_ERR_SKEW when the difference exceeds the allowable clock skew, typically five minutes by convention, and requires a replay cache to catch repeats inside that window, returning KRB_AP_ERR_REPEAT. The specification even requires that a server which loses its replay cache reject everything until the skew interval has passed.

Wrong: The Needham-Schroeder protocol was broken because its cryptography was weak. Right: No cryptography was broken in Lowe’s 1995 attack. The public-key protocol failed because the second message did not identify its sender, so an intruder with whom the initiator was legitimately communicating could relay her nonce to a third party and have the third party conclude she had authenticated to him. The fix is to add the responder’s identity to that message. The same class of omission — a field the response does not cover — is behind most protocol failures since.

Wrong: Channel binding and origin binding are two names for the same idea. Right: They bind different things and fail differently. Channel binding ties the response to a value unique to the transport connection, currently the tls-exporter type of RFC 9266 for TLS 1.3, and survives an attacker who has fraudulently obtained a certificate for the real name. Origin binding ties the response to the verifier’s DNS name, works through TLS terminators and load balancers, and does not survive such a certificate. NIST SP 800-63B revision 4 accepts both as phishing resistant while stating that channel binding “is considered more secure”.

Wrong: tls-unique is the standard TLS channel binding, so use it. Right: It was defined in RFC 5929 section 3 in July 2010 and is not suitable for TLS 1.3. The triple handshake attack of 2014 showed that without the extended master secret extension of RFC 7627 two different connections could share a Finished message and therefore a tls-unique value. RFC 9266 of July 2022 defines tls-exporter as the replacement: the TLS exporter with label “EXPORTER-Channel-Binding”, an empty context value and a length of 32 bytes.

Wrong: A user who is careful enough will not fall for a phishing page, so origin binding is a convenience rather than a control. Right: Origin binding removes the user from the decision entirely. The browser reports the origin, not the user; the authenticator signs what the browser reports; the real site rejects any assertion carrying a foreign origin. A user who is completely deceived, who intends to log in and tries repeatedly, still cannot produce anything the real site will accept. That is why CISA’s fact sheet of 31 October 2022 treats phishing resistance as a category distinct from every other form of multi-factor authentication rather than as a stronger grade of the same thing.

22.99 Chapter summary in 20 lines#

  1. A replay attack re-sends a message that was valid once in order to obtain its effect again, and it requires no cryptographic skill whatsoever.
  2. Challenge-response defeats replay by making the verifier ask a question that has never been asked before, so a recording of yesterday’s answer answers nothing.
  3. The three sources of freshness are a random nonce, a counter and a timestamp, and every real protocol uses one of them or a deliberate mixture.
  4. A random nonce is unpredictable and needs no shared state, but costs a round trip and requires the verifier to remember what is outstanding.
  5. A counter never repeats and needs no randomness, but it is predictable, which makes it a good replay detector and a bad challenge.
  6. A timestamp removes the round trip entirely, which is why large systems use it, at the cost of a shared clock, a tolerance window and a replay cache.
  7. Nonce length follows the birthday bound: 32 bits gives about a 39 per cent chance of a repeat after 65,536 challenges, while 128 bits is safe past a trillion.
  8. RFC 1994, published in August 1996, defines CHAP, whose response is a one-way hash over the Identifier, then the secret, then the challenge value, with algorithm 5 meaning MD5.
  9. RFC 1994 section 2.2 requires the secret to be held in plaintext by the verifier, which is the defining limitation of every symmetric challenge-response scheme.
  10. MS-CHAPv2, RFC 2759 of January 2000, added mutual authentication and was reduced by Marlinspike and Hulton at DEF CON 20 in July 2012 to the strength of a single DES encryption.
  11. Kerberos, specified in RFC 4120 of July 2005 and descended from RFC 1510 of September 1993 and MIT’s Project Athena of 1983, uses a key distribution centre, tickets and session keys.
  12. The Kerberos authenticator carries ctime and cusec and no challenge at all, so freshness comes from the client’s clock and the tolerance is typically five minutes.
  13. RFC 4120 section 3.2.3 returns KRB_AP_ERR_SKEW outside the tolerance and requires a replay cache keyed on server name, client name, time and microseconds, returning KRB_AP_ERR_REPEAT on a repeat.
  14. Needham and Schroeder in December 1978 established the trusted-third-party pattern, Denning and Sacco in August 1981 showed its replay weakness and proposed timestamps, and Kerberos implements their fix.
  15. Gavin Lowe’s attack of November 1995 broke the public-key protocol without breaking any cryptography, because the second message failed to name its sender; the fix is to add the responder’s identity.
  16. In a signature-based scheme the entire security rests on what the signature covers, so the byte layout of the signed data is a security specification and not a formatting detail.
  17. SSH signs the session identifier first, and TLS 1.3 signs sixty-four 0x20 octets, a context string, a zero separator and the handshake transcript hash, each element defeating a named attack.
  18. A challenge the responder can predict is not a challenge; the Cambridge pre-play work of September 2012 showed EMV terminals using counters and timestamps for the unpredictable number, allowing cryptograms to be harvested in advance.
  19. Channel binding ties the response to the connection, using tls-exporter from RFC 9266 of July 2022 with the label “EXPORTER-Channel-Binding” and a length of 32 bytes, after tls-unique from RFC 5929 was undermined by the triple handshake attack.
  20. Origin binding ties the response to the verifier’s name, which is what defeats real-time phishing, because the browser reports the origin honestly, the authenticator signs it, and the real site rejects any assertion that names somebody else.

Chapter sources: RFC 1994, “PPP Challenge Handshake Authentication Protocol (CHAP)”, W. Simpson, August 1996, obsoleting RFC 1334, sections 2.2 and 2.3 and the packet formats in sections 4.1 and 4.2; RFC 2759, “Microsoft PPP CHAP Extensions, Version 2”, G. Zorn, January 2000, informational; RFC 7616, “HTTP Digest Access Authentication”, Shekh-Yusef, Ahrens and Bremer, September 2015, obsoleting RFC 2617; RFC 5802, “Salted Challenge Response Authentication Mechanism (SCRAM) SASL and GSS-API Mechanisms”, Newman, Menon-Sen, Melnikov and Williams, July 2010, together with RFC 7677, “SCRAM-SHA-256 and SCRAM-SHA-256-PLUS SASL Mechanisms”, T. Hansen, November 2015, updating RFC 5802; RFC 4120, “The Kerberos Network Authentication Service (V5)”, Neuman, Yu, Hartman and Raeburn, July 2005, obsoleting RFC 1510, in particular section 1.6 on clock skew, section 3.2.3 on receipt of the KRB_AP_REQ message and the replay cache, section 5.3 for the Ticket and EncTicketPart structures and section 5.5.1 for the Authenticator and AP-REQ structures; RFC 1510, Kohl and Neuman, September 1993, for the historical statement of the Needham-Schroeder and Denning-Sacco lineage; RFC 4252, “The Secure Shell (SSH) Authentication Protocol”, Ylonen and Lonvick, January 2006, section 7 for the publickey signature field order; RFC 8446, “The Transport Layer Security (TLS) Protocol Version 1.3”, August 2018, section 4.4.3 for the CertificateVerify signature construction and worked example and section 7.5 for the exporter interface; RFC 5929, “Channel Bindings for TLS”, Altman, Williams and Zhu, July 2010, sections 3, 4 and 5; RFC 9266, “Channel Bindings for TLS 1.3”, S. Whited, July 2022, updating RFCs 5801, 5802, 5929 and 7677; RFC 7627 for the extended master secret extension, with Bhargavan, Delignat-Lavaud, Fournet, Pironti and Strub, “Triple Handshakes and Cookie Cutters: Breaking and Fixing Authentication over TLS”, IEEE Symposium on Security and Privacy, San Jose, April 2014, for the attack that prompted it; RFC 4086, “Randomness Requirements for Security”, Eastlake, Schiller and Crocker, June 2005, best current practice 106; RFC 8954, “Online Certificate Status Protocol (OCSP) Nonce Extension”, M. Sahni, November 2020, for the 1 to 32 octet nonce bounds; W3C “Web Authentication: An API for accessing Public Key Credentials - Level 3”, Candidate Recommendation Snapshot of 26 May 2026, for the authenticator data layout, the CollectedClientData fields and the assertion signature over the concatenation of authenticator data and client data hash; NIST Special Publication 800-63B revision 4, published July 2025 and effective 1 August 2025, sections 3.2.5 on phishing resistance, covering channel binding and verifier name binding, and 3.2.7 on replay resistance, with the summary of requirements table; Needham and Schroeder, “Using encryption for authentication in large networks of computers”, Communications of the ACM volume 21 number 12, December 1978, pages 993 to 999; Denning and Sacco, “Timestamps in key distribution protocols”, Communications of the ACM volume 24 number 8, August 1981, pages 533 to 535; Lowe, “An attack on the Needham-Schroeder public-key authentication protocol”, Information Processing Letters volume 56 number 3, November 1995, pages 131 to 136, and “Breaking and Fixing the Needham-Schroeder Public-Key Protocol Using FDR”, TACAS 1996, pages 147 to 166; Steiner, Neuman and Schiller, “Kerberos: An Authentication Service for Open Network Systems”, USENIX Conference Proceedings, Dallas, February 1988, pages 191 to 202; Bellovin and Merritt, “Limitations of the Kerberos Authentication System”, Computer Communications Review volume 20 number 5, October 1990, pages 119 to 132, and the Winter 1991 USENIX conference, Dallas, pages 253 to 267; Bond, Choudary, Murdoch, Skorobogatov and Anderson, “Chip and Skim: cloning EMV cards with the pre-play attack”, arXiv 1209.2531 of 12 September 2012, presented at the IEEE Symposium on Security and Privacy 2014; Brands and Chaum, “Distance-Bounding Protocols”, EUROCRYPT '93, published 1994, pages 344 to 359, and Desmedt, Goutier and Bengio, “Special Uses and Abuses of the Fiat-Shamir Passport Protocol”, CRYPTO '87, published 1988, for the mafia fraud; Marlinspike and Hulton, “Defeating PPTP VPNs and WPA2 Enterprise with MS-CHAPv2”, DEF CON 20, July 2012, with Microsoft’s security advisory of August 2012; 3GPP TS 33.102 for the UMTS authentication and key agreement field lengths, RAND at 128 bits, SQN and AK at 48 bits, AMF at 16 bits and MAC at 64 bits; and CISA’s fact sheets “Implementing Phishing-Resistant MFA” and “Implementing Number Matching in MFA Applications”, released 31 October 2022.