In this tutorial, we will learn what AES-128 is, and how to use it on an ESP32 to securely encrypt and decrypt data.
AES-128 encryption is a very secure symmetric encryption method widely used to protect data in embedded systems like the ESP32.
It uses a 128-bit secret key to encrypt and decrypt data, providing a very high level of security (basically unbreakable).
But before we see how to use it with an ESP32, let’s briefly look at what CBC AES-128 is.
What is CBC AES-128?
AES (Advanced Encryption Standard) is a symmetric encryption algorithm that uses a 128, 192, or 256-bit key to encrypt and decrypt data.
The algorithm was selected by the National Institute of Standards and Technology (NIST) as the encryption standard for the United States government in 2001 due to its efficiency and high security.
Key Features of CBC AES-128
- 128-bit key: AES-128 uses a 128-bit key, which offers a balance between security and efficiency in embedded systems.
- Block chaining: CBC chains data blocks, meaning each encrypted block depends on the previous block.
- Initialization vector (IV): CBC requires a unique IV for each encryption operation, ensuring that two identical messages produce different encrypted results.
128-bit encryption is the smallest of them and, logically, the one that requires the least resources. Nevertheless, the level of security it offers is very high.
AES-128 encryption requires that the data be multiples of 16 bytes. If the data does not meet this requirement, padding must be applied.
Meanwhile, CBC (Cipher Block Chaining) is a mode of operation that adds security to AES encryption by chaining data blocks, making each encrypted block depend on the previous one.
Expand to read more about AES operation modes
AES can operate in several modes, each with its own characteristics:
- ECB (Electronic Codebook): Each block is encrypted independently. It is not secure for long or repetitive data.
- CBC (Cipher Block Chaining): Each block is encrypted based on the previous block, improving security. It requires a unique IV.
- CTR (Counter): Converts AES into a stream cipher, allowing encryption of variable-length data.
Implementing CBC AES-128 on ESP32
The ESP32 uses the integrated AES-128 module in its hardware to perform encryption and decryption operations.
To access this module, we will use the mbedtls
library, which provides implementations of various cryptographic algorithms, including AES.
#include <mbedtls/aes.h>
To encrypt data using CBC AES-128, we will follow these steps:
- Initialize the AES context: Set up the AES context with the key and IV.
- Encrypt the data: Apply CBC AES-128 encryption to the data.
#include <mbedtls/aes.h>
#include <string.h>
void encrypt_cbc_aes128(const uint8_t *key, const uint8_t *iv, const uint8_t *input, uint8_t *output, size_t length) {
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
// Set the key
mbedtls_aes_setkey_enc(&aes, key, 128);
// Encrypt the data in CBC mode
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, length, iv, input, output);
mbedtls_aes_free(&aes);
}
The decryption process is similar to encryption, but using the MBEDTLS_AES_DECRYPT
function.
void decrypt_cbc_aes128(const uint8_t *key, const uint8_t *iv, const uint8_t *input, uint8_t *output, size_t length) {
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
// Set the key
mbedtls_aes_setkey_dec(&aes, key, 128);
// Decrypt the data in CBC mode
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, length, iv, input, output);
mbedtls_aes_free(&aes);
}
Complete Example
Here is the complete example of encryption and decryption using CBC AES-128 on the ESP32, neatly separated into files for your convenience 😉
#include "mbedtls/aes.h"
#include "mbedtls/cipher.h"
#define INPUT_LENGTH 16
void encrypt(char* input, char* key, unsigned char* output)
{
unsigned char iv[] = { 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, (const unsigned char*)key, strlen(key) * 8);
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, INPUT_LENGTH, iv, (const unsigned char*)input, output);
mbedtls_aes_free(&aes);
}
void decrypt(unsigned char* input, char* key, unsigned char* output)
{
unsigned char iv[] = { 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, (const unsigned char*)key, strlen(key) * 8);
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, INPUT_LENGTH, iv, (const unsigned char*)input, output);
mbedtls_aes_free(&aes);
}
void setup()
{
Serial.begin(115200);
delay(2000);
char plainText[] = "www.luisllamas.es";
const uint8_t plainTextSize = sizeof(plainText) / sizeof(char);
char key[] = "abcdefghijklmnop";
unsigned char cipherTextOutput[plainTextSize];
unsigned char decipheredTextOutput[plainTextSize];
encrypt(plainText, key, cipherTextOutput);
decrypt(cipherTextOutput, key, decipheredTextOutput);
Serial.println("\nOriginal plain text:");
Serial.println(plainText);
Serial.println("\nCiphered text:");
for(int i = 0; i < plainTextSize; i++)
{
char str[3];
sprintf(str, "%02x", (int)cipherTextOutput[i]);
Serial.print(str);
}
Serial.println("\n\nDeciphered text:");
for(int i = 0; i < plainTextSize; i++)
{
Serial.print((char)decipheredTextOutput[i]);
}
}
void loop() {}
And if you run it, you will see something like this
Extra bonus: ECB Encryption
As a bonus, here’s AES-128 encryption with ECB (instead of CBC). Unlike CBC, ECB does not require an initialization vector, which simplifies its implementation.
However, this makes it vulnerable to repeated patterns. That is, if two blocks of plaintext are identical, they will produce identical ciphertext blocks (which can reveal information about the structure of the message).
The ECB mode is not recommended for encrypting sensitive data due to its lack of randomization.
ECB Encryption
#include "mbedtls/aes.h"
void encrypt(char* plainText, char* key, unsigned char* outputBuffer)
{
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, (const unsigned char*)key, strlen(key) * 8);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, (const unsigned char*)plainText, outputBuffer);
mbedtls_aes_free(&aes);
}
void decrypt(unsigned char* chipherText, char* key, unsigned char* outputBuffer)
{
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_dec(&aes, (const unsigned char*)key, strlen(key) * 8);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_DECRYPT, (const unsigned char*)chipherText, outputBuffer);
mbedtls_aes_free(&aes);
}
void setup()
{
Serial.begin(115200);
delay(2000);
char plainText[] = "www.luisllamas.es";
const uint8_t plainTextSize = sizeof(plainText) / sizeof(char) +2;
char key[] = "abcdefghijklmnop";
unsigned char cipherTextOutput[plainTextSize];
unsigned char decipheredTextOutput[plainTextSize];
encrypt(plainText, key, cipherTextOutput);
decrypt(cipherTextOutput, key, decipheredTextOutput);
Serial.println("\nOriginal plain text:");
Serial.println(plainText);
Serial.println("\nCiphered text:");
for(int i = 0; i < plainTextSize; i++)
{
char str[3];
sprintf(str, "%02x", (int)cipherTextOutput[i]);
Serial.print(str);
}
Serial.println("\n\nDeciphered text:");
for(int i = 0; i < plainTextSize; i++)
{
Serial.print((char)decipheredTextOutput[i]);
}
}
void loop() {}