Security Tip: Use HMAC Hashes To Verify Data

[Tip#66] For those situations where you need to generate a repeatable hash or signature, reach for HMAC, rather than MD5 or SHA1.

Security Tip: Use HMAC Hashes To Verify Data

👉 Laravel Security Audit and Penetration Tests → I’m currently fully booked until late March, so if you’re thinking about an audit in Q1/Q2 2024, reach out now to reserve your slot! 🕵️


Let’s pick up from last week’s Security Tip: Do You Really Need a Hash for That? (Go read that first, if you haven’t yet!)

We’ve talked about situations where you don’t need to reach for hashes, but what if you do need a hash?

There are a few reasons why you’d want to hash some data, and today we’re going to cover the use case of verifiable signatures or repeatable hashes. These are hashes generated from a known piece of data, and then compared to another hash generated from a different piece of data. If the hashes match, then the data is the same and unmodified.

The simplest example of this are Laravel’s Signed URLs1. These work by generating the full URL, hashing that URL (using HMAC), and adding the hash signature onto the URL. When the user uses the URL, the application extracts the signature, hashes the provided URL and checks if the signature matches the new hash. If they match, the URL hasn’t been modified and can be trusted. If they don’t match, the URL cannot be trusted.

This sounds great, but how do we generate a hash like this? Also, how can we use this between different apps to protect things like API payloads? Using a raw hashing algorithm won’t prevent someone else from generating a hash of the modified data. Instead, we need to use a shared secret key2 known only to the servers which are generating and verifying the signatures, and then use this as part of generating our hash.

While you could just add a shared secret into the plaintext value you’re hashing3, a secure method is to use a Hash-based Message Authentication Code (HMAC) with PHP’s `hash_hmac()` function4:

hash_hmac(
    string $algo,
    string $data,
    string $key,
    bool $binary = false
): string

We can use it like this:

> $algo      = 'sha256';
> $plaintext = 'One ring to rule them all';
> $secretKey = 'secret';

> hash_hmac($algo, $plaintext, $secretKey);
= "f1ae7cb307846761e8c42d2b12c8c400328e8fd692996c5fb65e722e703d3825"

An attacker can control both the `$plaintext` and hash output, but without knowing the `$secretKey`, they cannot generate a hash that matches the modified `$plaintext`.

If you’re generating hashes within your application only, you could use `config('app.key');` directly as the HMAC key, but I would recommend generating and using a separate key for this5. This allows you to rotate keys independently, as well as share keys if needed.

As you’d expect, HMAC signatures are often used in APIs and especially webhooks, to prevent tampering and forging values. If you can verify the signature, you can trust the API & webhook payloads. Many API SDKs handle this component automatically for you.

If you’ve used HMACs or other forms of signatures and repeatable hashes before, I’d love to hear what you’re doing with them. Leave a comment below with the details!


Looking to learn more?
Security Tip #47: Getting Started with Content Security Policies
▶️ In Depth #16: What Are Insecure Functions?

  1. One of my favourite features in the framework, which I’ve written about before in depth.

  2. Shared Secret → This is the term used to describe a secret value known on both sides of a cryptographic operation, such as when you use HMAC hashes to generate signatures of API payloads to prevent tampering. Unlike public/private key pairs, a shared secret is the same on both sides.

  3. Please don’t do this.

  4. https://www.php.net/manual/en/function.hash-hmac.php

  5. Using separate keys for all of the different cryptographic operations in your app is a good idea. Laravel lets you override the keys for each of them.