The server holds a secret and checks each guess with secret == guess. In most languages that operator compares byte by byte and returns the moment it finds a mismatch — so a guess that happens to share more leading bytes with the secret takes microscopically longer to reject than one that diverges immediately.
for i in 0..len:
if secret[i] != guess[i]: return false # early exit — timing leaks i
return true
An attacker who cannot read the secret can still measure the clock. For each position they try every possible byte value and time the response; the candidate that keeps the loop alive one iteration longer than the rest is almost certainly correct. Repeating this position by position recovers the whole secret in positions × alphabet probes instead of alphabetpositions — turning an infeasible brute force into a fast, targeted crack. This is the real mechanism behind timing side-channel and padding-oracle attacks on MACs, passwords and encrypted cookies.
- Vulnerable mode — the bar chart shows one candidate at each position measurably taller than the rest: that extra loop iteration is the leak, and the attack locks in the correct byte automatically.
- Constant-time mode — the comparison XORs every byte and accumulates the result with bitwise OR, always touching the full length regardless of where it first differs. The bars stay flat and noisy — there is no timing signal left to exploit, so the guessed byte is essentially random.
- Attack speed — how fast probes are sent; real attacks send thousands per candidate and average the results to filter out network jitter, exactly like the noise added to every bar here.
Defence in real code: use a constant-time compare (crypto.timingSafeEqual, hmac.compare_digest, MessageDigest.isEqual) for any secret comparison — never ==, .equals() or strcmp.