Timing Attack — recovering a secret from how long a secret-dependent operation takes

verified · provenanceused 0× by assistantsside-channel

A timing attack recovers a secret from the *execution time* of a secret-dependent operation — not from any error message or explicit oracle bit, purely from how long the response took.

The signal that gives it away

- A server validates a token/HMAC/password/signature and you can measure round-trip latency; the check is a naive ==/memcmp-style comparison that returns (or branches) as soon as it hits the first mismatched byte, rather than a constant-time comparison. This is the classic "byte-at-a-time via timing" shape. - A remote endpoint that decrypts or signs with RSA, where the underlying modular exponentiation is a textbook square-and-multiply: the runtime is proportional to the number of 1-bits processed in the exponent, because a "square" happens on every bit but the extra "multiply" only fires when that bit is 1. That data-dependent branch is the leak. - Textual/contextual tells: instructions to "guess the signature/token", a remote oracle with no explicit valid/invalid response field (contrast with Error / Boolean Oracle Attack — turn any distinguishable valid/invalid response into a key or plaintext, which has an explicit boolean), jittery-but-statistically-separable response times, or a challenge that explicitly hands you a time() measurement per query instead of a pass/fail flag.

How it's exploited

The recipe that held up in practice: recover a secret byte-by-byte, picking at each position the candidate byte that maximizes mean/median latency, because a longer-matching prefix keeps a short-circuiting comparison running longer before it bails out on the mismatch.

```python import time, requests, statistics

def t(tok): samples = [] for _ in range(40): start = time.perf_counter() requests.post(URL, data={"sig": tok}) samples.append(time.perf_counter() - start) return statistics.median(samples)

known = "" for _ in range(32): best = max("0123456789abcdef", key=lambda c: t(known + c + "0" * (31 - len(known)))) known += best ```

Operational details that made the difference between a working attack and a pile of noise: - Average many samples per candidate (tens, not one) — a single timing sample is not a boolean, it's a noisy real number, and network/server jitter alone can flip which candidate looks slower on any individual trial. - Use the median, not the mean, to reject outliers — a handful of slow samples from an unrelated GC pause or a co-resident process spike will drag a mean but not a median. - Run locally or on the lowest-latency network path available whenever the challenge allows it — network jitter is usually the dominant noise source, larger than the signal itself over the internet; local timing measurement is the difference between a clean signal and an unusable one. - The same core logic — signal proportional to secret-dependent work — extracts RSA private-key bits from decryption/signing timing: an implementation using plain square-and-multiply (not constant-time Montgomery ladders) leaks the Hamming weight and, with enough samples, the bit pattern of the exponent, because every 1-bit costs one extra multiply that a 0-bit doesn't.

Related

Cache-Timing Attack — recovering key bytes from which cache line a table lookup touched for the microarchitectural cousin (which cache line was touched, not how long it took) — reach for that instead when the leak is table-lookup-shaped and the challenge exposes cache-line-level detail rather than raw latency; Power Analysis (SPA/DPA/CPA) — reading a key off a device's power draw for the same "measure something external, infer the key" family via power draw instead of time; Error / Boolean Oracle Attack — turn any distinguishable valid/invalid response into a key or plaintext for the broader boolean-oracle methodology this attack is a noisy special case of; Cryptanalysis Methodology — classify the family before you reach for an attack for where this fits in the overall triage.

What does NOT work

- Trusting a single timing sample as ground truth. One request's latency is dominated by noise (network jitter, server load, GC pauses) far more than by the microsecond-scale signal from a comparison short-circuit. Picking the "best" candidate off one measurement per option produces confidently wrong bytes that look plausible but are not; the fix is always statistical — many samples, median aggregation, and ideally a significance check before locking a byte in. - Measuring over a noisy or highly-variable network path and expecting the signal to survive. If jitter is larger than the timing difference the leak produces, no amount of the same naive sampling recovers it — this is where "run locally" or "reduce sample count variance via warm-up requests" made the difference between an unusable dataset and a working attack, not a smarter statistic applied after the fact. - Assuming timing leaks apply to any slow endpoint. The leak has to be *secret-dependent* — a uniformly slow comparison (constant-time, or one that always processes the full length regardless of match) gives nothing to distinguish; confirm the timing actually varies with a controlled prefix-length experiment (feed a known-correct prefix of increasing length and check latency trends upward) before investing in the full byte-recovery loop. - Confusing this with a boolean/error oracle. If the endpoint already returns an explicit valid/invalid signal (status code, error body, boolean field), building timing-measurement machinery on top is wasted effort — go straight to Error / Boolean Oracle Attack — turn any distinguishable valid/invalid response into a key or plaintext's query-efficient methods instead of the noisier latency-based approach.

Verified against

18 claims checked against these sources

Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.