--- id: wiki-2026-0508-timing-attack title: Timing Attack category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Timing Side Channel, Constant Time Comparison] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [security, side-channel, cryptography, constant-time] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: nodejs --- # Timing Attack ## 매 한 줄 > **"매 execution time 의 secret-dependent variation 의 leak"**. 1996 Kocher RSA timing paper origin — comparison/branch/cache 가 secret bit 에 dependent 면 attacker 가 multiple measurement 의 statistical analysis 로 secret 을 추출. Mitigation 의 핵심: constant-time code. ## 매 핵심 ### 매 Vulnerable patterns - **Early-exit string compare**: `==` / `strcmp` returns on first mismatch byte. - **Secret-dependent branch**: `if (key[i] == 0)` 의 cache miss timing differs. - **Secret-dependent table lookup**: AES S-box → cache line timing. - **Modular exponentiation**: square-and-multiply 의 bit-dependent ops. ### 매 Mitigation - **Constant-time compare**: scan all bytes regardless, XOR-accumulate. - **No secret-dependent branches**: use bitwise mask instead. - **No secret-dependent indices**: scan full table or use bit-slicing. - **Blinding**: randomize input (RSA: blind w/ random r, decrypt, unblind). ### 매 응용 1. HMAC token comparison (auth bypass via timing). 2. Password hash compare (after bcrypt — still need constant-time). 3. JWT signature verify. ## 💻 패턴 ### Vulnerable: early-exit compare ```typescript // ❌ DON'T — leaks prefix length function badCompare(a: string, b: string): boolean { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; // early exit reveals match length } return true; } // Attacker measures: "aaaa" vs "baaa" faster than "aaaa" vs "aaab" ``` ### Constant-time compare (Node.js) ```typescript import { timingSafeEqual } from 'node:crypto'; function safeCompare(a: string, b: string): boolean { const bufA = Buffer.from(a); const bufB = Buffer.from(b); if (bufA.length !== bufB.length) { // length leak unavoidable — pad to fixed length OR accept timingSafeEqual(bufA, bufA); // dummy compare to equalize timing return false; } return timingSafeEqual(bufA, bufB); } // HMAC token check import { createHmac } from 'node:crypto'; function verifyToken(token: string, payload: string, secret: string): boolean { const expected = createHmac('sha256', secret).update(payload).digest('hex'); return expected.length === token.length && timingSafeEqual(Buffer.from(expected), Buffer.from(token)); } ``` ### Browser: SubtleCrypto + manual constant-time ```typescript // Web Crypto has no timingSafeEqual — implement carefully function ctEqualBytes(a: Uint8Array, b: Uint8Array): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) { diff |= a[i] ^ b[i]; // XOR-accumulate, no branch } return diff === 0; } async function verifyHmac(msg: string, sig: ArrayBuffer, key: CryptoKey) { const computed = await crypto.subtle.sign( 'HMAC', key, new TextEncoder().encode(msg) ); return ctEqualBytes(new Uint8Array(computed), new Uint8Array(sig)); } ``` ### Python constant-time compare ```python import hmac def verify(token: str, expected: str) -> bool: return hmac.compare_digest(token, expected) # bcrypt — already constant-time via library import bcrypt def check_password(plain: bytes, hashed: bytes) -> bool: return bcrypt.checkpw(plain, hashed) # safe internally ``` ### Go constant-time ```go import "crypto/subtle" func verify(a, b []byte) bool { return subtle.ConstantTimeCompare(a, b) == 1 } // Conditional copy without branch func ctSelect(cond int, a, b []byte) { subtle.ConstantTimeCopy(cond, a, b) } ``` ### Branchless conditional (C-style) ```typescript // Constant-time conditional select function ctSelect(cond: number, a: number, b: number): number { // cond must be 0 or 1 const mask = -cond; // 0 or 0xFFFFFFFF return (a & mask) | (b & ~mask); } // Constant-time min/max without branch function ctMin(a: number, b: number): number { const lt = (a - b) >>> 31; // 1 if a