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.
đ 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?
One of my favourite features in the framework, which Iâve written about before in depth.
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.
Please donât do this.
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.