I recently was asked to explain some code that used an HMAC signature for authenticating/verifying a message. While I understood that HMACs are used for message authenticity, I did not fully grasp the implementation. Here is the information I have discovered through more research.
MAC stands for "message authentication code". Essentially, it is a way to generate a code to be submitted with a message that verifies the sender of the message and also verifies the message has not been modified. HMAC stands for "hash-based message authentication code". It uses a cryptographic hashing algorithm to generate the MAC. An HMAC algorithm works by hashing a message along with a secret key. The resulting hash is called a signature or digest. The message is then transferred to a recipient along with the signature. If the recipient has the secret key, they can hash the message with the same algorithm and verify the resulting signature matches the one sent with the message. As a result, the message is simultaneously authenticated and its integrity verified.
We can try to better understand an HMAC through an example. Let us imagine that you have a doctor's office application that needs to send an HTTP request to a secure API that contains medical history for patients. The message you want to send to the API is a JSON packet containing the results of the latest visit to the doctor by the patient.
{
"first_name": "Andrew",
"last_name": "Davis",
"visit": "2020-01-12 09:56:12",
"symptoms": [
"sore throat",
"runny nose",
"sneezing",
"headache"
],
"diagnosis": "Common Cold",
"suggested_treatment": [
"drink water",
"rest",
"consume ibuprofen twice a day"
]
}
The API has provided you with a secret key that might look something like this 13441c78e4440683a9e32aa7e4ee39fd3544b87968cc5a30f621f7e32029b2f5
and says you must use it to create a SHA-256 HMAC signature to pass along in the Authorization
HTTP header for requests.
Our programming language of choice is PHP. Thankfully, PHP already has functions for generating HMAC signatures. To see all the supported algorithms, you can run hash_hmac_algos()
and PHP will return an array of all the hash types supported. Here is the list for PHP 7.4 on my machine.
>>> hash_hmac_algos();
=> [
"md2",
"md4",
"md5",
"sha1",
"sha224",
"sha256",
"sha384",
"sha512/224",
"sha512/256",
"sha512",
"sha3-224",
"sha3-256",
"sha3-384",
"sha3-512",
"ripemd128",
"ripemd160",
"ripemd256",
"ripemd320",
"whirlpool",
"tiger128,3",
"tiger160,3",
"tiger192,3",
"tiger128,4",
"tiger160,4",
"tiger192,4",
"snefru",
"snefru256",
"gost",
"gost-crypto"
"haval128,3",
"haval160,3",
"haval192,3",
"haval224,3",
"haval256,3",
"haval128,4",
"haval160,4",
"haval192,4",
"haval224,4",
"haval256,4",
"haval128,5",
"haval160,5",
"haval192,5",
"haval224,5",
"haval256,5",
]
We see that SHA-256 is supported by PHP. SHA-256 is a common hashing algorithm created by the Unites States' NSA which is often used with HMAC algorithms.
Now, we need to create the actual HMAC signature of our message. To do so, we can use PHP's hash_hmac
function. The first parameter is the algorithm, the second parameter is the message and the third parameter is the secret key.
>>> hash_hmac('sha256',\
... '{"first_name": "Andrew","last_name": "Davis","visit": "2020-01-12 09:56:12","symptoms": ["sore throat","runny nose","sneezing","headache"],"diagnosis": "Common Cold","suggested_treatment": ["drink water","rest","consume ibuprofen twice a day"]}',\
... '13441c78e4440683a9e32aa7e4ee39fd3544b87968cc5a30f621f7e32029b2f5');
=> "0def47f4878406abf864da0d34f9e3627653317e7c77da8230e0f65b52c09479"
PHP responds with a message digest/signature: 0def47f4878406abf864da0d34f9e3627653317e7c77da8230e0f65b52c09479
. We can submit the JSON message to the API along with the signature. The API will then take the message and it will create an HMAC signature using SHA-256 and its secret key. If its resulting HMAC signature matches the HMAC you provided, then it accepts the message for processing.
The API exchange highlights the two main benefits of HMAC.
First, since an HMAC requires a secret key, both the sender and recipient have to have the same secret key for HMAC verification to work. As a result, the HMAC acts similarly to a password. It is very important to keep the secret key safe. Since it is used like a password, it needs to be saved in encrypted storage. It is also important to use a sufficiently long secret key that cannot be brute forced by password breakers.
Second, an HMAC is based not only on the secret key, but also on the message itself. If two HMACs are created with the same secret key, but different messages, the resulting codes will be different. When the API sees that its HMAC matches the HMAC in the request, it knows the request was not modified during transport. Since hackers will try to modify messages in between clients and servers, HMAC signatures can be a powerful tool to verify authenticity of requests.
Hopefully this explanation has shed some light on HMAC signatures. Unfortunately, I do not have the math and cryptography experience to explain how the HMAC algorithm works to create the signature. Maybe someone else in the DEV Community can provide a layman's explanation of the computation.
What is your experience with HMAC and where have you seen it used it effectively?
Top comments (2)
Looks like there is some misunderstandings, so I'll help.
Symmetric: MACs are not digital signatures. You can't verify the authenticity of someone's message without a secret key. Call it a MAC, or HMAC. This can allow for plausible deniability. These are easier to use, and understand like most symmetric cryptographic primitives. And generally more secure than asymmetric ones because of that.
You should use MAC/HMACs if you don't need someone else to verify your messages. <- This doesn't apply in all cases!
Asymmetric: Digital signatures imply anyone can verify the authenticity of a message, given a public key to a corresponding private key that was used to sign the message. Just like signing a document with your signature - everyone can verify that it was you who signed it. Nor can you deny you signed it.
You use this especially when signing software releases. Or signing emails with PGP.
Cryptographic keys are not passwords! Do not treat them like they are. Of course, make sure to keep them secret. Most of the time you use cryptographically secure pseudo-random numbers for the keys. Sometimes we use a Key Derivation Function to derive a cryptographic key from a low entroy value (passwords).
Length extension attacks are the reason we have HMACs. Primitives such as SHA1, SHA-256, MD5 are vulnerable, and that's just to name a few. Those hash functions make use of the Merkle–Damgård construction, which length extension attacks stemmed from. There are newer algorithms that aren't vulnerable, such as BLAKE2, or SHA-3.
I won't explain the details here, as the links are excellent sources to follow up on.
I just wrote a script this week for generating HMAC signatures so I could work with an API in Postman! So thankful that Postman supports pre-request scripting.