skip to content

For authenticator codes, how many time steps of clock drift should a verifier accept, and what does each extra step cost?

level: seniorimportance: should knowfreq 48%

answer

  1. a window is a guess multiplier
  2. 2w+1 codes accepted per submission
  3. one step of delay is RECOMMENDED
  4. measure the offset, do not widen
  5. window and attempt cap price together

basics

~20 s

Accept as few as real handsets require - RFC 6238 Section 6 RECOMMENDS at most one time step of delay. Each extra accepted step adds another valid code, so the window and the attempt cap are one decision rather than two.

solid answer

~50 s

A code is computed from the shared secret and a counter derived from the clock, and the two clocks need not agree, so a verifier accepts a small band of steps either side of its own. Keep that band tight: RFC 6238 Section 6 RECOMMENDS allowing at most one time step for delay. The reason is arithmetic - a window of plus or minus one step means three of the million six-digit values are accepted on any single submission, plus or minus two means five, and every widening multiplies what one guess is worth. That is why the window and the verification attempt cap are one decision: widening the window silently enlarges what the cap has to bound. When one handset is genuinely out of synch, record the offset seen at its last successful verification on that enrolment row and re-centre its window, rather than paying for that clock on every account.

code

pseudocode · 13 lines
pseudocode
STEP = 30            # seconds per time step
T0   = 0             # step counting starts here

def match_step(secret, submitted, now, window, offset_steps):
    centre = floor((now - T0) / STEP) + offset_steps
    for k in -window .. +window:                 # window = 1 -> 3 candidates
        if constant_time_equal(submitted, code_for(secret, centre + k)):
            return centre + k                    # the step that actually matched
    return NO_MATCH

# six-digit codes, window = 1  ->  3 of 1_000_000 accepted per submission
# six-digit codes, window = 2  ->  5 of 1_000_000 accepted per submission
# widening from 1 to 2 makes a single blind guess 67% more likely to land

go deeper

for a junior

Know why a window exists: the handset's clock and the server's clock are independent, so the verifier checks a few steps either side. And know that the guidance is a recommendation, not a requirement.

for a middle

Do the arithmetic out loud. A window of w steps either side means 2w+1 accepted codes per submission, and say what that does to the odds on a single blind guess against six digits.

for a senior

Price it. Tie the window to the attempt cap explicitly, and show the per-enrolment offset as the alternative to widening globally for one bad clock. Name which of the two controls is load-bearing if the other is removed.

for a principal

Own the trade as a service decision: how much support cost a tight window generates in the field, what a wider one silently concedes, and why that choice belongs with the attempt cap rather than in a separate configuration review.

## Why a window exists at all A time-derived code is `HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))` with the counter `C` computed from the clock: the seconds since `T0` divided by the step size, RECOMMENDED at 30 seconds. The prover and the verifier each compute `C` from **their own** clock, and those clocks do not agree. In a livestock movement-licensing service the disagreement is not hypothetical. Holders record movements from vehicle cabs and shed doorways on handsets that have been out of signal for days, whose clock is whatever it last synchronised to. On top of that sits ordinary latency: the holder reads the digits, types them, the form posts over a marginal connection, and the step has rolled over before the request lands. So the verifier computes the code for its own current step and for a small number of steps either side, and accepts a match on any of them. That band is the **drift window**, and it is the only tolerance in the mechanism. ## What each extra step costs A window of `w` steps either side means `2w + 1` candidate codes are accepted on every submission. With six-digit codes, that is directly a guess multiplier. | window (steps either side) | codes accepted per submission | six-digit values in play | versus plus or minus one | |---|---|---|---| | 0 | 1 | 1 in 1,000,000 | - | | plus or minus 1 | 3 | 3 in 1,000,000 | baseline | | plus or minus 2 | 5 | 5 in 1,000,000 | +67% | | plus or minus 5 | 11 | 11 in 1,000,000 | +267% | The table is the whole argument. Going from one step to two does not sound like much and is a two-thirds increase in what a single blind guess is worth. Going to five steps - a common panic response to a support queue - more than triples it. Two details are worth keeping straight: - **The window does not change how long a code lives for the holder.** A code is valid for its step; the window changes how many *steps* the verifier will entertain, which is a different quantity. - **The window is not automatically symmetric.** Latency and typing both push a submission into the past, so a verifier that accepts one step behind and none ahead covers the common case at two thirds of the exposure. Accepting steps ahead buys tolerance only for handsets whose clocks run fast, which is a measurable population rather than an assumption. ## The window and the attempt cap are one decision The defence against blind guessing is a cap on failed verification attempts, and the window sets how much work that cap has to do. At plus or minus two steps, each submission is checked against five valid values instead of one, so the same cap is bounding five times the space. Widen the window without revisiting the cap and you have quietly weakened a control you never touched. How that cap is built - what it counts, where its counter lives, what the response says when it trips - is a separate design question. What this decision owes the cap is a window small enough to be worth capping. ## Drift is systematic, so measure it rather than widen for it The instinct when confirmations fail is to widen the window for everyone. That is the wrong shape, because handset clock error is not noise - it is a **persistent offset**. One holder's device is forty seconds behind and stays forty seconds behind. The better move is per-enrolment: 1. When a code verifies, note **which** candidate step matched - the residual offset relative to the verifier's own step. 2. Store that offset on the enrolment row. 3. Re-centre that enrolment's window on the stored offset next time, keeping the band the same width. The result is that a chronically slow handset is tolerated without paying for it on every other account, and the global window stays at the RECOMMENDED width. The holder should still be nudged to re-synchronise the device clock - that is the actual fix, and the offset is a shock absorber, not a cure. ## Read the rule's strength correctly RFC 6238's guidance here is a **RECOMMEND**, not a MUST. That matters twice over in an interview. It means a verifier may legitimately choose something else and owns the consequence; and it means a candidate who reports it as a hard requirement has misread the document, which is the same error that produces cargo-cult configuration elsewhere. The defensible answer is therefore not a number recited from a specification. It is: start at the RECOMMENDED width, measure what real handsets actually need through per-enrolment offsets, and treat any proposal to widen the global band as a proposal to weaken the attempt cap by the same factor.

  • A handset is consistently three steps behind. Do you widen the window for everyone?
    No. Record the offset observed at that enrolment's last successful verification on its own row and re-centre its window on it, keeping the band the same width. A global widening prices every other account's exposure to fix one clock. The holder should still be told to re-synchronise the device, since the offset is a shock absorber rather than the fix.
  • How does the accepted window change what a verification attempt cap has to cover?
    It multiplies what one guess is worth. At plus or minus two steps a single submission is checked against five valid values instead of one, so the same cap is bounding five times the space. How the cap itself is built - what it counts and what the response says - is a separate design question; what the window owes it is to stay small enough to be worth capping.
  • Should the window be symmetric?
    Not necessarily. Network latency and typing both push a submission into the past, so accepting one step behind and none ahead covers the common case at two thirds of the exposure of a symmetric band. Accepting steps ahead buys tolerance only for handsets whose clocks run fast - worth doing if you have measured that population, not as a default.

saying these in an interview costs you the question

  • Widens the drift window until the support tickets stop
  • Treats the window and the verification attempt cap as unrelated settings
  • Says a wider window is free because each code still expires
  • Reads the RFC 6238 recommendation as a hard requirement
  • Assumes handset clocks drift randomly rather than by a persistent offset