Timing Attacks: Why Your Code Might Be Leaking Secrets

Have you ever heard about side-channel attacks? They are a category of security attacks where attackers instead of breaking cryptography directly, analyze unintended information that "leaks" from the physical implementation of a system during normal operation.
Side-channel attacks are dangerous - even perfectly implemented cryptography can be vulnerable because of information leakage from the physical implementation of cryptographic systems. They may be invisible to standard security audits as it requires interdisciplinary knowledge to understand and prevent.
There are several types of the attack: timing attacks, cache timing attacks, power analysis attacks and others.
Timing attacks are one of the most common and accessible side-channel attacks because:
- They can be performed remotely over networks
- Don't require physical access to hardware
- Can be executed with standard software tools
- Often affect web applications and APIs directly
Understanding timing attacks
Instead of attacking the mathematical solution of your encryption algorithms, these attacks target how your code actually runs, allowing attackers to extract passwords, private keys, and other sensitive data simply by measuring response times. Timing attacks present unique challenges because they can bypass traditional security measures entirely.
In 2003, researchers David Brumley and Dan Boneh demonstrated extracting complete 1024-bit RSA private keys from SSL servers over a network using only timing analysis—requiring about one million queries over two hours. Their attack worked despite network jitter and proved that timing attacks are real danger for real systems.
The core principle is deceptively simple: execution time often correlates with specific properties of the secret like values and length.
For example, consider this condition:
javascript1'HELLO' === 'AAAA';
It is technically vulnerable because JavaScript engines (V8, SpiderMonkey) implement string comparison with an early exit for performance.
Testing "AAAA" against string "HELLO" fails on the first character (fastest), while "HAAA" fails on the second character (slightly slower), and "HEAA" fails on the third (even slower). An attacker can determine the correct string character-by-character, reducing the search space from exponential to linear complexity.
This is important - it is not a practical example because:
- Timing differences are tiny - nanoseconds, not milliseconds
- Network jitter drowns out these tiny differences
- Nobody compares passwords directly (I hope so)
Let's consider more practical examples.
Real timing attacks exploit operations that take milliseconds, not nanoseconds. When a database lookup or cryptographic operation creates measurable delays, attackers can extract secrets even over noisy networks.
Authentication systems under attack
Authentication systems represent the highest-value target for timing attacks in web applications. When your authentication takes different amounts of time based on whether a username exists, or when your password comparison exits early on the first wrong character, you're broadcasting sensitive information through timing patterns.
javascript1function authenticate(username, password) {2 const user = database.getUser(username);34 if (!user) {5 return false; // Fast return - major timing leak6 }78 return bcrypt.compare(password, user.hashedPassword); // Slow operation9}
When an invalid username is provided, the function returns immediately. For valid usernames, the expensive bcrypt.compare()
operation executes, creating a timing difference that's easily detectable across network connections.
Attackers can build complete lists of valid usernames before launching targeted password attacks.
API rate limiting and timing analysis
Rate limiting systems, designed to prevent abuse, often become timing attack vectors themselves. Timing-based rate limit detection allows attackers to map rate limiting thresholds and develop evasion strategies:
javascript1// Rate limiting creates timing patterns2app.use(3 rateLimit({4 windowMs: 15 * 60 * 1000, // 15 minutes5 max: 100, // Limit requests per window6 handler: (req, res) => {7 res.status(429).send('Too many requests'); // Immediate response8 },9 })10);1112// Normal request: 200-500ms response13// Rate limited request: <10ms response (timing leak)
Rate-limited requests typically return immediately with error codes, while normal requests undergo full processing. This timing difference enables attackers to detect rate limiting activation and adjust their attack patterns accordingly.
What to do with the attack on the rate limiting systems? You may see suggestions like hiding rate limits. Because visible rate limiting allows attackers to:
- Map exact thresholds (e.g., "I can make exactly 99 requests before hitting the limit")
- Optimize attack patterns to stay just under limits
- Rotate through different IPs/sessions more efficiently
- Time their attacks to reset windows perfectly
The idea is that uncertainty forces attackers to be more cautious. However, this is generally considered "security through obscurity" and not very effective - attackers can still discover the limits through probing.
I would suggest to consider rate limits as public information. They just should be sensible, but they may not be secrets.
You may learn more about rate-limiting at Backend Rate Limiting
Modern defensive strategies and countermeasures
Defending against timing attacks requires a multi-layered approach combining constant-time programming, architectural defenses, and monitoring systems. The fundamental principle is ensuring that execution time remains independent of secret data.
Constant-time comparison functions
Constant-time comparison functions are the cornerstone of timing attack defense. Modern platforms provide built-in timing-safe comparison functions.
Dummy‐hash trick for passwords: Make your login endpoint always invoke the slow password check, whether the username exists or not, so an attacker can’t time‐probe “does this user exist?”
javascript1// Precompute a dummy hash once at startup:2const DUMMY_HASH = '$2b$12$K9QhMWzVN5YbRz5sXl0ueODhk0PtAyp7cVt2hx6Vj7XOx0JPTWm6W';34// In your login handler, always do bcrypt.compare:5async function login(username, password) {6 const user = await db.findUser(username);7 // If user is missing, compare against the dummy hash8 const hashToCompare = user ? user.hash : DUMMY_HASH;9 const passwordValid = await bcrypt.compare(password, hashToCompare);1011 if (!user || !passwordValid) {12 return { error: 'Invalid credentials' };13 }14 return { token: generateToken(user) };15}
Bcrypt’s .compare()
is already designed to run in constant time per hash, but if you ever compare raw secrets (HMACs, tokens, API keys, etc.), you should never use ===
. Instead do something like
javascript1const { timingSafeEqual } = require('crypto');23function safeCompare(a, b) {4 const bufA = Buffer.from(a);5 const bufB = Buffer.from(b);67 // If lengths differ, compare bufA to itself (constant time)8 // to prevent leaking the length of the secret9 if (bufA.length !== bufB.length) {10 timingSafeEqual(bufA, bufA);11 return false;12 }1314 // true constant-time compare15 return timingSafeEqual(bufA, bufB);16}
Architectural defenses
Architectural defenses are system design patterns that eliminate timing attack vectors by restructuring how your application processes sensitive operations. Instead of fixing timing leaks in code, you redesign the system so timing information becomes meaningless to attackers.
Key Architectural Patterns:
- Request/Response Decoupling
- Response Caching Strategy
- Uniform Processing Pipelines
- Service Segregation
Let's look closer at Request/Response Decoupling:
javascript1// app/api/auth/forgot-password/route.js2import { addToQueue } from '@/lib/queue';34export async function POST(request) {5 const { email } = await request.json();67 // Queue the task instead of processing inline8 addToQueue('password-reset', {9 email,10 requestId: crypto.randomUUID(),11 timestamp: Date.now(),12 });1314 // Return response immediately15 return Response.json({16 message: "If an account exists, you'll receive reset instructions",17 });18}1920/** Background worker processes queue (runs separately) **/21// lib/workers/password-reset.js22export async function processPasswordReset({ email }) {23 const user = await findUserByEmail(email);2425 if (user) {26 await sendPasswordResetEmail(user);27 }28 // No timing information leaks to the original requester29}
Monitoring systems
Monitoring systems are detection and analysis tools that identify timing attack attempts by analyzing request patterns, response times, and behavioral anomalies. Unlike rate limiting systems that prevent requests, monitoring systems are designed for detection. They observe and alert on suspicious patterns without blocking the traffic, allowing you to analyze potential attacks.
Start protecting your code today
Defending against timing attacks starts with three simple changes to your codebase:
- Switch all secret comparisons to timing-safe functions
- Implement dummy processing for authentication failures
- Use libraries with built-in timing attack protection
Timing attacks are subtle but real. They won't show up in your unit tests or security scans. The best defense is understanding how they work and building protection into your code from the start by incorporating timing attack considerations into architectural decisions, development processes, and monitoring systems.
Conclusion
While the fundamental attack principles remain unchanged since Paul Kocher's pioneering work in 1996, timing attacks now affect a much broader range of applications - from browser JavaScript to backend services.
Success in defending against timing attacks requires combining multiple defensive layers: constant-time implementations at the code level, architectural protections like rate limiting and response normalization, comprehensive monitoring for attack detection, and regular security assessments to identify emerging vulnerabilities. By implementing these defenses systematically, development teams can build applications that resist even sophisticated timing attacks while maintaining the performance and functionality that users expect.
Also published on: