Security Tip: Compare keys with hash_equals()

[Tip#56] It may be tempting to compare keys/sensitive strings using `===`, or even `==`, but that opens you up to timing attacks! You should be using a timing attack safe string comparison function...

Security Tip: Compare keys with hash_equals()

It may be tempting to compare keys, or other sensitive strings, using the strict comparison === (or even the loose comparison == 😱) however this opens up to timing attacks, which can be used to identify the keys you’re comparing.

But first, what do I mean by “keys” or “sensitive strings”?

These are string values that the user does not know but needs to provide in order to do or access something. Common examples are API keys, passwords, tokens, secret phrases, answers, etc… Some apps will use these a lot, while others might not ever need them.

Consider this code:

// api.secret_token => 'V1cHt2S67DADJIm9sX9yzCc272EkSC'

if ($request->token === config('api.secret_token')) {
    // Do something...
}

In this example, the user needs to provide a valid token before they are allowed to do something. However, since the strict comparison in use (===) is not timing attack safe, an attacker can eventually figure out the value by providing specific values of $request->token and measuring the timing of the response.

💡
It may take a lot of requests, and a simple rate limiter can make this significantly harder, but it’s still a very real problem that you need to take seriously.

The solution is simple, all you need to do is to use hash_equals() to compare the two strings! This ensures the comparison always takes exactly the same amount of time regardless of the inputted string, preventing an attacker from figuring out the secret key:

// api.secret_token => 'V1cHt2S67DADJIm9sX9yzCc272EkSC'

if (hash_equals(config('api.secret_token'), $request->token)) {
    // Do something...
}
💡
Note, it’s only “exactly the same” when both values are the same length. However, figuring out the length isn’t overly helpful if you can’t also figure out what it contains.

Since it’s a simple function that replaces your strict (or loose) comparison, it’s trivial to implement, so there really is no reason not to use it. 🙂

You can learn more about hash_equals() in the PHP docs: https://www.php.net/manual/en/function.hash-equals.php.

If you want to learn more about timing attacks, go check out In Depth #6: Timing Attacks, where we covered them in detail, including a section about string comparisons which directly relates to this issue. You should check out Anthony Ferrara’s “It’s All About Time” article to dive deeper into timing attacks.