Timing-Safe Comparison with timingSafeEqual

Timing-Safe Comparison with timingSafeEqual

TL;DR

Use timingSafeEqual when comparing sensitive strings.

import { timingSafeEqual } from "crypto";
function safeCompare(provided: string, expected: string): boolean {
const providedBuffer = Buffer.from(provided);
const expectedBuffer = Buffer.from(expected);
if (providedBuffer.length !== expectedBuffer.length) {
return false;
}
return timingSafeEqual(providedBuffer, expectedBuffer);
}

The Problem

Why === is Vulnerable

When comparing two strings, it’s tempting to use the === operator:

function validateApiKey(provided: string, expected: string): boolean {
return provided === expected; // Vulnerable!
}

The problem is that === short-circuits on the first mismatched character. Attackers can measure these timing differences to guess the string character by character.

Timing Attack Example

String comparisons are frequently needed for authorization checks, such as validating JWT tokens, API keys, etc.

Attackers can send multiple requests, measuring response times to incrementally guess the secret:

Terminal window
Attempt 1: "a_live_abc123xyz" 0.102ms (fails at index 0)
Attempt 2: "s_live_abc123xyz" 0.108ms (slightly slower - 's' matches!)
Attempt 3: "sa_live_abc123xyz" 0.103ms (fails at index 1)
Attempt 4: "sk_live_abc123xyz" 0.115ms (even slower - 'sk' matches!)

The Solution

What is timingSafeEqual?

timingSafeEqual is a function from Node.js’s crypto module that compares two buffers in constant time, preventing timing attacks.

Node.js Documentation

How It Works

Unlike ===, timingSafeEqual compares every byte, taking the same amount of time regardless of whether the strings match or have varying lengths.

import { timingSafeEqual } from "crypto";
function safeCompare(provided: string, expected: string): boolean {
const providedBuffer = Buffer.from(provided);
const expectedBuffer = Buffer.from(expected);
if (providedBuffer.length !== expectedBuffer.length) {
return false;
}
return timingSafeEqual(providedBuffer, expectedBuffer);
}

When to Use

Use timingSafeEqual when comparing: