Sign Up Docs Tech Blog Product Blog

Understanding timing attacks with code examples

Vulnerable login example#

The following code snippet has a subtle security issue with it. Can you tell what’s wrong?

// Returns true if the email/password pair is valid
async function isValidCredentials(emailAddress, password) {
    // Fetch the password hash from the DB by email address
    const passwordHashOrNull = await fetchPasswordHash(emailAddress);

    // If there was no match, return false
    if (!passwordHashOrNull) {
        return false;

    // Bcrypt is "a library to help you hash passwords"
    // Here we use the compare function to check that the
    //   provided password matches the hashed password in the DB
    const doesPasswordMatch = await, passwordHashOrNull);
    return doesPasswordMatch;

// Fetches the password hash from the DB
async function fetchPasswordHash(emailAddress) {
    // impl not important

As a hint, let’s look at how long a few calls to isValidCredentials takes:

async function timeIsValidCredentials(emailAddress, password) {
    console.time("Checking " + emailAddress);
    await isValidCredentials(emailAddress, password);
    console.timeEnd("Checking " + emailAddress);

await timeIsValidCredentials("", "password");
// Checking 63.813ms
await timeIsValidCredentials("", "password2");
// Checking 62.867ms
await timeIsValidCredentials("", "password");
// Checking 4.017ms
await timeIsValidCredentials("", "password");
// Checking 4.008ms

There’s a noticeable difference between how long emails take and or

It turns out that the issue is these lines:

  // If there was no match, return false
if (!passwordHashOrNull) {
    return false;

By returning early if there was no match, an attacker can easily tell that has an account, but and don’t.

Timing attacks#

This is a common example of a timing attack. They are a class of attacks where the length of time that your application takes to perform a task leaks some information.

In the login case, the difference in times made it pretty obvious from even one request. If the difference was more subtle, an attacker can make many requests over a long time and average them together to distinguish different cases.

Is it a big deal?#

This might not seem like a big deal, but let’s say I’m trying to find someone’s personal email. I only have their name, and I know they have signed up for your site. I can try a bunch of variations of or lastname{3digitnumber} and so on until I find a valid one.

Additionally, there are other timing attacks that leak even more sensitive information, which we’ll see in a bit.

How can we fix it?#

There are a few strategies, but the simplest answer is “make sure all codepaths take the same amount of time”. You don’t have to do this everywhere, just in sensitive parts of the codebase.

Instead of returning early, we could have checked the password against some hash and then returned false:

// If there was no match, waste time and then return false
if (!passwordHashOrNull) {
    return false;

It is also useful to add rate limiting whenever possible. If an attacker needs a lot of requests to distinguish different cases, rate limiting them could make the attack impractical.

Timing attacks in practice#

Recently, a clever timing attack was found in' password reset . It exploited the fact that databases when comparing two strings will return early if the strings don’t match.

So checking

"a".repeat(10000) === "b".repeat(10000)

should take less time than

"a".repeat(10000) === "a".repeat(9999) + "b"

This means that the more characters you have correct, the longer the call will take. An attacker could try different prefixes and see which one takes the longest to slowly determine a valid password reset token.

This same vulnerability exists anywhere where someone is checking a secret value directly against a database, so while it may seem pretty theoretical, there are definitely real world cases that have been reported and fixed.

PropelAuth © 2022