hash_equals
Compares two strings in constant time so the comparison cannot leak information through timing.
hash_equals(string $known_string, string $user_string): bool- Added in PHP 5.6.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$known_string | string | The known string, which must be kept secret. Marked #[\SensitiveParameter] so it is redacted from stack traces. |
$user_string | string | The user-supplied string to compare against. Always pass the untrusted value here, as the second argument. |
Return value
booltrue when the two strings are equal, false otherwise.
Errors
Throws a TypeError if either argument is not a string.
hash_equals() compares two strings byte by byte and always takes the same amount of time for a given input length, regardless of where the first difference occurs. PHP's === operator short-circuits on the first mismatched byte, and that timing difference is measurable over a network.
When to reach for it
Use it every time you compare a secret against something a user sent: API keys, webhook signatures, CSRF tokens, password-reset tokens, session identifiers, and the output of hash_hmac(). If an attacker controls one side of the comparison, this is the function you want.
What trips people up
Argument order matters. The known secret goes first, the untrusted value second; the manual is explicit that it is important to provide the user-supplied string as the second parameter rather than the first.
Length is the one thing this function cannot hide. When the two strings differ in length it returns false immediately, and the length of the known string may be leaked to a timing attack. In practice that is fine, because you should be comparing fixed-length digests rather than raw secrets — hash both sides first if their lengths could vary.
Finally, it is a plain string comparison, not a verifier. It does not know about password hashing; for that use password_verify(), which is already constant time.
Example
When to use it
- Verify an incoming webhook's HMAC signature header against one you computed.
- Check an API key sent in a request against the key stored for that account.
- Compare a submitted CSRF token with the one held in the session.
Frequently asked questions
Why not just use === to compare tokens?
=== stops at the first differing byte, so a wrong guess that shares a longer prefix takes measurably longer to reject. Over many requests that timing signal lets an attacker recover the secret one byte at a time.
Does hash_equals hide the length of the strings?
No. If the lengths differ it returns false straight away. Hash both values first so they are always the same fixed length.
Can hash_equals be used to check a password?
Not against a stored password hash — use password_verify(), which handles the salt and cost embedded in the hash and is already timing safe.
More examples
Comparing variable-length secrets safely
Both operands are now 64 characters no matter how long the original keys were, so the early length check can never reveal anything. This is the standard pattern for API key comparison.
<?php
$storedKey = 'k_live_averylongapikeyvalue';
$submitted = 'k_live_averylongapikeyvalue';
// Hash both sides so length never varies and never leaks.
$a = hash('sha256', $storedKey);
$b = hash('sha256', $submitted);
var_dump(hash_equals($a, $b));
var_dump(hash_equals($a, hash('sha256', 'k_live_wrong')));
echo strlen($a), ' == ', strlen($b), PHP_EOL;A CSRF token check
The helper returns true only for the exact session token. The isset() guard matters because passing null would raise a TypeError before hash_equals() ever ran.
<?php
$session = ['csrf' => bin2hex(random_bytes(16))];
$post = ['csrf' => $session['csrf']];
function validCsrf(array $session, array $post): bool {
return isset($post['csrf'])
&& hash_equals($session['csrf'], (string) $post['csrf']);
}
var_dump(validCsrf($session, $post));
var_dump(validCsrf($session, ['csrf' => 'tampered']));
var_dump(validCsrf($session, []));
Discussion