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):

// insecure.php (simplified example)$expected = getenv('GITHUB_API_TOKEN');// e.g. 'V1cHt2S67DADJIm9sX9yzCc272EkSC'$provided = $_GET['token'] ?? '';if ($provided === $expected) { // grant access} 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):

// secure.php$expected = getenv('GITHUB_API_TOKEN'); $provided = $_GET['token'] ?? '';if (hash_equals($expected, $provided)) { // grant access} 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()

  1. 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.
  2. 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.
  3. Logging and monitoring
    Recognize unusual patterns (many token attempts, systematic variation of certain parameters) and react automatically.
  4. Salt / HMAC / Signatures
    Use signed tokens (HMAC with secret key) or OAuth tokens instead of openly comparing raw static keys.
  5. TLS/HTTPS
    Encryption protects the communication channels - although it does not prevent timing attacks on server internals, it is a basic requirement.
  6. 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.
About the author
Founder and CEO of Langmeier Software
I don't want to complicate anything. I don't want to develop the ultimate business software. I don't want to be listed in a top technology list. Because that's not what business applications are about. It's about making sure your data is seamlessly protected. And it's about making sure everything runs smoothly while you retain full control and can focus on growing your business. Simplicity and reliability are my guiding principles and inspire me every day.
 
Look it up further:
PHP, Security awareness, Cybersecurity
Related articles