Preventing Timing Attacks When Comparing Password Hashes
A timing attack exploits the fact that a naive string comparison (like === in JavaScript, or a simple loop comparing characters one at a time) returns as soon as it finds the first mismatched character — meaning the comparison takes very slightly longer when more of the beginning of the string matches correctly. In theory, an attacker who can measure that timing difference precisely enough, across many repeated attempts, can gradually guess a secret one character at a time.
This sounds academic, but it's a real, documented category of attack, particularly relevant to anything comparing secret tokens, API keys, or hash values — session tokens, password reset tokens, HMAC signatures, and API key validation are all realistic targets, especially over a network where an attacker can send many requests and statistically average out noise in the timing measurements.
It's worth being precise about where this actually applies: if you're using a proper password hashing library like bcrypt to verify a login password, the library's own comparison function is already designed to be timing-safe internally — this is specifically why you should always use the library's verify function rather than hashing the input yourself and comparing the two hash strings with a plain equality check.
The risk shows up specifically when developers write their own comparison logic for tokens or signatures outside of a library that already handles this — for example, manually checking whether an incoming webhook signature matches an expected HMAC value using a simple string equality check, rather than a constant-time comparison function built for exactly this purpose.
The fix is using a constant-time comparison function, which always takes the same amount of time regardless of where (or whether) the two values differ, typically by comparing every character regardless of early mismatches, or by comparing hashes of the values instead of the raw values themselves. Node.js provides crypto.timingSafeEqual() for exactly this purpose, and most other languages have an equivalent in their standard cryptography library.
One practical detail: crypto.timingSafeEqual() requires both buffers being compared to be the same length, and throws if they aren't — since comparing lengths first is itself technically a (much smaller and generally less exploitable) timing signal, some implementations hash both values to a fixed length before the constant-time comparison, sidestepping the length-mismatch issue entirely.
As a general rule: any time you're comparing a value that came from a user or an external system against a secret your server holds — a webhook signature, an API token, a CSRF token — reach for a constant-time comparison function rather than a plain equality check, even if the practical exploitability in your specific case seems low. It costs almost nothing to do correctly and closes off an entire category of subtle vulnerability.
Found this helpful?
SyncTonight's tools and guides are free and always will be. If this post saved you some debugging time, a coffee goes a long way — no pressure, just appreciated.
☕ Buy me a coffee