The objective of this esp32 tutorial is to explain how to apply the HMAC mechanism to a message on the ESP32, using the Arduino core. The tests of this ESP32 tutorial were performed using a DFRobot’s ESP32 module device integrated in a ESP32 development board.
Introduction
The objective of this post is to explain how to apply the HMAC mechanism to a message on the ESP32, using the Arduino core.
We will use the mbed TLS library, which is supported by the ESP32 and available in the Arduino core without the need to install any additional library.
As a brief introduction, HMAC is a mechanism for message authentication that uses hash functions under the hood [1]. The strength of the HMAC is related with the strength of the used hash function [1], so it needs to be chosen accordingly to the application.
The HMAC can be used with any iterated cryptographic hash function [1]. In our case, we will use the SHA-256 function and thus the output HMAC code will have 32 bytes.
The sender of the message generates the HMAC code and sends it with the message. The recipient can then generate the code again from the message and check if it matches the one received.
As mentioned, the HMAC is used to ensure message authentication, which is achieved by having a shared secret (usually called a key) between the two communicating entities [1].
Since this key is only known by the two parties and is used in the HMAC mechanism, only those two parties should be able to generate the message authentication code, thus guaranteeing authentication.
Note that key length impacts the cryptographic strength of the HMAC and thus longer keys are more secure [1]. Also take in consideration that the keys used should be randomly generated [1]. Nonetheless, for simplicity, we will not use a random key on this example code.
Additionally to authentication, we can use this mechanism to check the integrity of the message [2], since if a possible attacker changes something in the message, the recipient of the message will generate a HMAC code that will not match the one received, thus discarding the message.
Additionally, since the attacker doesn’t know the secret key, he cannot tamper with the message and generate a new valid code.
The tests of this ESP32 tutorial were performed using a DFRobot’s ESP32 module device integrated in a ESP32 development board.
The code
The first thing we need to do is including the md.h header file, which exposes a generic interface for mbed TLS which wraps all the hashing functions it has available [3].
#include "mbedtls/md.h"
We will write the remaining code on the setup function. The first thing we will do is opening a serial connection, to later output the results of the HMAC operation.
Serial.begin(115200);
Next we need to specify the two inputs of the function, namely the message to which we want to obtain the code and the secret key. Remember that you should use random keys for real application scenarios.
char *key = "secretKey";
char *payload = "Hello HMAC SHA 256!";
We will also need to declare a byte array to store the authentication code generated by applying the HMAC. As mentioned in the introductory section, the output will have 32 bytes, which should be the size of the array.
byte hmacResult[32];
Now that we have both the key and the message, we will need to declare some additional variables that are used by the functions of the library.
So, we will need a mbedtls_md_context_t struct which will be used by all the mbed TLS functions we will call. This context is used to keep internal state between the function calls [3].
mbedtls_md_context_t ctx;
We will also need to get the enumerated value that represents the type of hashing algorithm to be used in the HMAC. The possible values are defined in the mbedtls_md_type_t enum,
In our case, we want to use the SHA-256 hashing algorithm, so the corresponding enum value is MBEDTLS_MD_SHA256.
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
Since the functions of the API will need to receive the length of both the message and the key, we will store those values in two variables. Since these variables are strings, we can obtain their lengths with the strlen function.
const size_t payloadLength = strlen(payload);
const size_t keyLength = strlen(key);
Finally, we are ready to call the functions from the mbed TLS library. The first one we need to call is the mbedtls_md_init function, which receives as input a pointer to a variable of type mbedtls_md_context_t.
This function will initialize the context we previously declared. Thus, we will pass as input the address for the ctx variable (remember that the function receives a pointer). Note that this function returns void.
mbedtls_md_init(&ctx);
Next we need to call the mbedtls_md_setup function, which selects the hash function to use and allocates the internal structures [4].
The first argument it receives is a pointer to a variable of type mbedtls_md_context_t.
The second argument is a variable of type mbedtls_md_info_t, which contains some information about the hash function that will be used. More details about the contents of this structure can be found here, but as example it contains fields such as the name of the function or the size of the output [5].
Note however that we haven’t declared any variable of this type, but rather just an enumerated value representing the hash function we want to use.
To obtain the corresponding mbedtls_md_info_t structure for the hash function we want to use, we can simply call the mbedtls_md_info_from_type function, passing as input the previously declared mbedtls_md_type_t variable.
Going back to the mbedtls_md_setup function, there’s still a third parameter that we need to specify, which is an integer that specifies if we will use HMAC (1) or not (0) [4]. In case the HMAC is not used, passing 0 will allow to save some memory [4]. Since we are going to use HMAC, we pass the value 1.
On success, the function returns 0 and on error it can return one of two values:- MBEDTLS_ERR_MD_BAD_INPUT_DATA for parameter failure;
- MBEDTLS_ERR_MD_ALLOC_FAILED for memory allocation failure.
Both these error values are defined on the md.h file and can be used for error checking. To keep things simple, we will assume everything will execute fine and thus we are not including any error validation.
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
Moving on, we need to call the mbedtls_md_hmac_starts function, which is used to set the HMAC key and prepare to authenticate [6].
Thus, besides the pointer to the context variable, this function receives as second input the key and has third input the length of the key, which we have both defined in variables.
Note that the function receives the key as a constant, so we need to make a cast on our variable.
This function also returns zero on success or MBEDTLS_ERR_MD_BAD_INPUT_DATA if parameter verification fails [6].
mbedtls_md_hmac_starts(&ctx, (const unsigned char *) key, keyLength);
Next we need to call the mbedtls_md_hmac_update function to pass the message to authenticate.
The signature of this function is similar to the previous one. It receives as first input the pointer to our context, as second the message as a constant and as third the length of the message.
The return value is also 0 for success and MBEDTLS_ERR_MD_BAD_INPUT_DATA if parameter verification fails.
mbedtls_md_hmac_update(&ctx, (const unsigned char *) payload, payloadLength);
Now that we have supplied both the key and the message, we will call the mbedtls_md_hmac_finish function to get the output authentication code.
As first argument, the function receives the pointer to the context and as second a buffer of bytes for the result.
The return value is 0 for success and MBEDTLS_ERR_MD_BAD_INPUT_DATA if parameter verification fails.
mbedtls_md_hmac_finish(&ctx, hmacResult);
To finalize the mbed TLS API calls, we need to call the mbedtls_md_free, which frees and clears the internal structures of the context [7]. The function returns void.
mbedtls_md_free(&ctx);
Now that we have the buffer with all the bytes of the HMAC output, we will iterate through its 32 bytes and print them to the serial port in hexadecimal format.
We will use the sprintf function to format each byte in a two characters length hexadecimal string.
We will use the %02x format specifier, which specifies that we want to print each byte has a hexadecimal string with two characters, padded with a leading zero if needed. This zero padding is important because the result for each byte of the array will be appended. If you want to learn more about these format specifiers, please check here.
You can check the final full source code below, which already includes the printing loop.
#include "mbedtls/md.h"
void setup(){
Serial.begin(115200);
char *key = "secretKey";
char *payload = "Hello HMAC SHA 256!";
byte hmacResult[32];
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
const size_t payloadLength = strlen(payload);
const size_t keyLength = strlen(key);
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, (const unsigned char *) key, keyLength);
mbedtls_md_hmac_update(&ctx, (const unsigned char *) payload, payloadLength);
mbedtls_md_hmac_finish(&ctx, hmacResult);
mbedtls_md_free(&ctx);
Serial.print("Hash: ");
for(int i= 0; i< sizeof(hmacResult); i++){
char str[3];
sprintf(str, "%02x", (int)hmacResult[i]);
Serial.print(str);
}
}
void loop(){}
Testing the code
To test the code, we simply need to upload it to the ESP32 using the Arduino IDE. After the upload finished, open the serial monitor. You should get an output similar to figure 1, which shows the HMAC code for our message.
Figure 1 – HMAC calculated on the ESP32.
To confirm our result is correct, we can use this online tool, which allows to compute the HMAC of a message using different hash functions. In our case, we will use the SHA-256.
So, simply input the same message and key used in the Arduino code and click the “Compute HMAC” button. You should get an output similar to figure 2, where the hash matches the one obtained from the Arduino code.Figure 2 – HMAC output from online tool.