Timing Attacks in Node.js
Ali nazari

Ali nazari @silentwatcher_95

About: Just a tech

Location:
Earth 🌍
Joined:
Jul 30, 2022

Timing Attacks in Node.js

Publish Date: Apr 22
29 15

Let’s start from the very beginning:

What a timing attack is (CWE‑208), why it matters in Node.js, and exactly how you can stop it.

  • CWE stands for Common Weakness Enumeration, a catalog of software security flaws.

  • CWE‑208 covers flaws where an attacker learns secret data (passwords, keys, etc.) simply by measuring how long certain operations take.

if your code takes a slightly different amount of time depending on secret data, an attacker can “listen to the clock” and gradually recover that secret.

micheal scot

What’s a timing (side‑channel) attack?

The side‑channel idea

Imagine you’re behind a curtain watching two people entering a dark room and opening a safe with a code lock. You can’t see the lock, but you hear the clicks.

If one person pauses longer before entering the final digit, you realize they got three digits right and hesitated on the fourth. By measuring those pauses, you learn the code—without ever seeing it.

In computing, a side‑channel is any unintended “signal” (time, power use, sound) that leaks information about what’s happening inside.

A timing attack is a side‑channel attack that focuses on tiny time differences.

Why timing matters

Most programming languages stop a compare operation (“do these two values match?”) as soon as they find a mismatch.

  • Compare abcX vs abcY: mismatch on the 4th character → stop after 4 checks.
  • Compare a000 vs b000: mismatch on the 1st character → stop after 1 check.

An attacker can repeatedly try different guesses and carefully measure “how long did the server take to reject it?”

Over thousands of requests, those micro‑differences reveal which characters were matching.

How do timing leaks show up in Node.js?

Node.js, by default, uses C libraries or pure‑JavaScript routines that often bail out early on a mismatch. Common pitfalls:

1.String or buffer comparison:

if (userInput === secret) { /* … */ }
Enter fullscreen mode Exit fullscreen mode

Behind the scenes, this is a variable‑time operation.

2.Conditional logic on secrets:

if (password.startsWith('admin')) {
  // do something slower
} else {
  // do something faster
}
Enter fullscreen mode Exit fullscreen mode

Even tiny branches can leak a few microseconds.

3.Hashing without constant‑time checks:

You compute a hash of the password, then do hash === storedHash. That comparison can leak.

Example: leaking a 4‑digit PIN

1.Attacker sends PIN “0000” → server checks 0 vs stored 0 (✓), then 0 vs stored 0 (✓), then 0 vs stored 0 (✓), then 0 vs stored 1 (✗) → rejects after 4 steps → takes 1.2 ms.

2.Attacker sends “0001” → mismatch on 4th step → also ~1.2 ms.

3.Attacker sends “0010” → mismatch on 3rd step → takes ~0.9 ms.

By comparing 1.2 ms vs 0.9 ms, the attacker knows the first two digits were correct (“00”) and the third was wrong.

In a handful of rounds, all four digits leak. 💦

micheal scot 2

How to prevent timing attacks in Node.js

  • Use constant‑time comparison

Node.js provides a built‑in for this:

import { timingSafeEqual } from 'node:crypto';

const a = Buffer.from(userComputedHash, 'hex');
const b = Buffer.from(storedHash, 'hex');
if (timingSafeEqual(a, b)) {
  // They match (no timing leak)
} else {
  // They don’t match
}
Enter fullscreen mode Exit fullscreen mode

Why it’s safe: it always walks through every byte of both buffers, doing the same work no matter where the first difference is.

Key point: wrap every secret compare in timingSafeEqual, even tiny flags or tokens.

  • Throttle brute‑force with slow hashes (key‑derivation)

Even if you hide timing leaks, an attacker could still try billions of guesses per second.

To slow them down, use a computationally expensive hash:

import { scrypt } from 'node:crypto';

// on user signup:
const salt = randomBytes(16).toString('hex');
scrypt(password, salt, 64, (err, derivedKey) => {
  // store { salt, derivedKey.toString('hex') }
});

// on login:
scrypt(attempt, storedSalt, 64, (err, attemptKey) => {
  const a = Buffer.from(attemptKey, 'hex');
  const b = Buffer.from(storedDerivedKey, 'hex');
  if (timingSafeEqual(a, b)) { /* good */ }
});
Enter fullscreen mode Exit fullscreen mode
  • Salt: a random per‑user string so identical passwords don’t look the same.

  • Work factor (N): increases CPU/memory cost. Higher N → slower hashes → better brute‑force resistance.


  • Avoid secret‐dependent branching

Any code that does “if secretValue do slow thing, else fast thing” can leak.

Refactor so that both paths take equal time, or better yet, separate secret logic into a constant‑time function.

  • Add fixed response padding

If you have any other early‑exit condition (e.g. “user not found”), add an artificial delay so every failure response takes the same total time:

const FIXED_DELAY = 200; // ms

async function handleLogin(...) {
  const start = Date.now();
  let ok = false;
  // … perform constant‑time compare or simply leave ok=false if user not found
  const elapsed = Date.now() - start;
  await new Promise(r => setTimeout(r, Math.max(0, FIXED_DELAY - elapsed)));
  if (ok) { /* success */ }
  else   { /* generic “invalid” */ }
}
Enter fullscreen mode Exit fullscreen mode
  • Layer on rate‐limiting and account lockouts

Even with slow hashes, attackers can spread attempts across many IPs.

Use tools like express-rate-limit or a firewall to cap attempts per minute, and temporarily lock accounts after repeated failures.

happy boss

This is your boss after you read this article.👆 lol


Hey! I recently created a tool called express-admin-honeypot.

Feel free to check it out, and if you like it, consider leaving a generous star on my GitHub! 🌟


Let's connect!!: 🤝

LinkedIn
GitHub

Comments 15 total

  • Ali nazari
    Ali nazariApr 22, 2025

    Send this post to someone who needs to read this before Monday.

  • daniele
    danieleApr 22, 2025

    How does it handle high inconsistency in response time of a request in the network? How can it work?

    • Ali nazari
      Ali nazariApr 22, 2025

      By enforcing a fixed minimum server‐side processing time, you guarantee every response leaves the server at the same moment.

      Any extra network jitter then just adds random noise, so attackers can’t tease out microsecond‑scale differences—even over an inconsistent connection.

      • daniele
        danieleApr 23, 2025

        So did you just confirm fixed minimum server reply time is useless based on the fact that there is always random noise since internet is an incosistent network?

        • Ali nazari
          Ali nazariApr 23, 2025

          consider this example:

          Imagine you're trying to hide a whisper in a room:

          • If the room is silent, people might hear your whisper.
          • But if the room has lots of random noise (like a party 🎉), your whisper gets lost in the chaos.

          Now:

          • The server’s timing differences (like failing early when a password is wrong) are like that whisper.
          • The internet (with random delays) is the noisy party.
          • But if your server sometimes whispers, sometimes shouts, patterns might still be found.

          p.s : i got this example from chatgpt 🤣

          • daniele
            danieleApr 23, 2025

            I see the point and yes, in theory this can be done.
            The topic is interesting.

            However I just need to PING google.com
            time=31.390 ms
            time=25.018 ms
            time=28.266 ms
            time=24.029 ms
            time=27.678 ms
            to answer myself this is unusable in RL.

            • Ali nazari
              Ali nazariApr 23, 2025

              Next time my boss tells me to add this feature, I’ll show him this comment as proof that it’s useless—thanks! 🗿🫡🤣

              • daniele
                danieleApr 24, 2025

                Maybe you'll save him money.

                • Ali nazari
                  Ali nazariApr 24, 2025

                  you're right, I just can’t stop being everyone’s hero.

  • nadeem zia
    nadeem ziaApr 23, 2025

    Thanks for sharing info

  • Nevo David
    Nevo DavidApr 24, 2025

    super helpful breakdown honestly - makes me want to double check my stuff. you ever caught a timing leak in your own code before or did you only learn about it later?

    • Ali nazari
      Ali nazariApr 24, 2025

      Thanks! Honestly, I just patch what I can and pray to the dev gods no one ever reports a timing attack on my stuff 🗿🤲🏻

  • DevByJ
    DevByJJun 12, 2025

    Hi Ali!

    Thank you for raising my awareness with this topic!
    Thinking back, I don’t think I’ve ever worked on a web application where this was taken into account 😳

    It’s time to change this!

    • Ali nazari
      Ali nazariJun 13, 2025

      Happy to hear you're inspired to change that! :)

Add comment