Software development
Timing attacks in PHP: A practical example - why `hash_equals()` matters
Would you like a realistic example of a timing attack on API tokens in Wordpress/Laravel/PHP? We cover it here, including tips and best practice measures against timing attacks.
Many developers rely on the simple comparison using === or == when checking API keys, tokens or other secret strings. At first glance, this looks reasonable - but there is a catch: timing differences in the comparison can be exploited by attackers.
This article describes a realistic example of how a timing attack works conceptually.
The real scenario (simplified example)
Imagine an internal microservice accepts requests with a query parameter token and compares it against a GitHub API token stored in the environment.
Insecure code (simplified):
$expected = getenv('GITHUB_API_TOKEN');$provided = $_GET['token'] ?? '';if ($provided === $expected) { } PHP
I will explain why this is problematic in the next section.
How does a timing attack work?
If the implementation of the token comparison compares character-by-character and aborts immediately at the first error, then a request with the correct first character will take a little longer on average.
A correct first character therefore leads on average to a slightly longer processing time than an incorrect first character. The same applies to the second character and so on.
An attacker takes advantage of this by testing all possible characters for each position, measuring the response times very often and evaluating them statistically. The character with the largest average/proportionately longer time is probably correct. In this way, the token is reconstructed step by step.
Why hash_equals() is the right choice
PHP provides a constant-time string comparison function with hash_equals(). It ensures that the execution time of the comparison does not depend on common prefixes - as long as both strings are the same length.
Safe alternative (for Laravel, PHP, Wordpress):
$expected = getenv('GITHUB_API_TOKEN'); $provided = $_GET['token'] ?? '';if (hash_equals($expected, $provided)) { } PHP
Note: hash_equals() is only "time-independent" if the lengths are equal. It is good practice to design tokens so that the length is constant (e.g. HMACs, fixed-length UUIDs or random fixed-length Base64 strings).
Did you know? Only UUID-V4 is completely randomly generated and is therefore often used for security tokens. Because it contains no time or device-related information, it offers a high degree of unpredictability and thus protects against attacks based on patterns or predictions. This is precisely why UUID-V4 is the preferred choice when it comes to secure, hard-to-guess identifiers.
Additional defenses - more than just hash_equals()
- Constant token length
Use tokens with a fixed length, e.g. UUID-V4. If an attacker guesses the length, this is less helpful as long as the characters themselves are secure.
- Rate limiting
Limit requests per IP or per account. Timing attacks require many measurements; rate limiting significantly increases the effort and costs for the attacker.
- Logging and monitoring
Recognize unusual patterns (many token attempts, systematic variation of certain parameters) and react automatically.
- Salt / HMAC / Signatures
Use signed tokens (HMAC with secret key) or OAuth tokens instead of openly comparing raw static keys.
- TLS/HTTPS
Encryption protects the communication channels - although it does not prevent timing attacks on server internals, it is a basic requirement.
- Token rotation
Short lifespan for tokens reduces the benefit of a compromised token.
Conclusion
Timing attacks are not a purely theoretical problem - they have been investigated in practice and can have serious consequences, especially for publicly accessible endpoints. The good news: For PHP/Laravel/Wordpress developers, the first countermeasure is very simple:
- Replace direct comparisons of sensitive strings with
hash_equals().
- Ensure constant token lengths, rate limiting, logging and secure token management.