En este tutorial aprenderemos qué es AES-128, y cómo utilizarlo en un ESP32 para cifrar y descifrar datos de manera segura.
El cifrado AES-128 es un método de encriptación simétrica muy seguro ampliamente utilizado para proteger datos en sistemas embebidos como el ESP32.
Utiliza una clave secreta de 128 bits para cifrar y descifrar los datos, lo que lo proporciona significa una nivel de seguridad altísimo (básicamente irrompible).
Pero antes de ver cómo usarlo con un ESP32, vamos a ver brevemente qué es el CBC AES-128.
¿Qué es CBC AES-128?
AES (Advanced Encryption Standard) es un algoritmo de cifrado simétrico que utiliza una clave de 128, 192 o 256 bits para encriptar y desencriptar datos.
El algoritmo fue seleccionado por el Instituto Nacional de Estándares y Tecnología (NIST) como el estándar de cifrado para el gobierno de los Estados Unidos en 2001 debido a su eficiencia y alta seguridad.
Características clave de CBC AES-128
- Clave de 128 bits: AES-128 utiliza una clave de 128 bits, lo que ofrece un equilibrio entre seguridad y eficiencia en sistemas embebidos.
- Encadenamiento de bloques: CBC encadena bloques de datos, lo que significa que cada bloque cifrado depende del bloque anterior.
- Vector de inicialización (IV): CBC requiere un IV único para cada operación de cifrado, lo que asegura que dos mensajes idénticos produzcan resultados cifrados diferentes.
El cifrado de 128bits es el menor de ellos y, lógicamente, el que requiere menos recursos. Pese a ello, el nivel de seguridad que ofrece es muy alto.
El cifrado AES-128 requiere que los datos sean múltiplos de 16 bytes. Si los datos no cumplen con este requisito, es necesario aplicar un relleno (padding).
Por su parte, el CBC (Cipher Block Chaining) es un modo de operación que añade seguridad al cifrado AES al encadenar bloques de datos, haciendo que cada bloque cifrado dependa del anterior.
Expande para leer más sobre modos de operación AES
AES puede operar en varios modos, cada uno con sus propias características:
- ECB (Electronic Codebook): Cada bloque se encripta de forma independiente. No es seguro para datos largos o repetitivos.
- CBC (Cipher Block Chaining): Cada bloque se encripta en función del bloque anterior, lo que mejora la seguridad. Requiere un IV único.
- CTR (Counter): Convierte AES en un cifrado de flujo, permitiendo encriptar datos de longitud variable.
Implementación de CBC AES-128 en ESP32
El ESP32 utiliza el módulo AES-128 integrado en su hardware para realizar operaciones de cifrado y descifrado.
Para acceder a este módulo, utilizaremos la biblioteca mbedtls
, que proporciona implementaciones de varios algoritmos criptográficos, incluido AES.
#include <mbedtls/aes.h>
Para cifrar datos utilizando CBC AES-128, seguiremos los siguientes pasos:
- Inicializar el contexto AES: Configurar el contexto AES con la clave y el IV.
- Cifrar los datos: Aplicar el cifrado CBC AES-128 a los datos.
#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);
// Configurar la clave
mbedtls_aes_setkey_enc(&aes, key, 128);
// Cifrar los datos en modo CBC
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, length, iv, input, output);
mbedtls_aes_free(&aes);
}
El proceso de descifrado es similar al de cifrado, pero utilizando la función MBEDTLS_AES_DECRYPT
.
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);
// Configurar la clave
mbedtls_aes_setkey_dec(&aes, key, 128);
// Descifrar los datos en modo CBC
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, length, iv, input, output);
mbedtls_aes_free(&aes);
}
Ejemplo completo
Aquí os dejo el ejemplo completo de cifrado y descifrado utilizando CBC AES-128 en el ESP32, separadito en archivos para que lo tengáis más a mano 😉
#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() {}
Y si lo ejecutáis veréis algo como esto
Bonus extra: Cifrado ECB
Os dejo de premio el cifrado AES-128 con ECB (en lugar de ECB). A diferencia de CBC, ECB no necesita un vector de inicialización, lo que simplifica su implementación.
Sin embargo, esto lo hace vulnerable a patrones repetidos. Es decir, si dos bloques de texto plano son idénticos, producirán bloques cifrados idénticos (lo que puede revelar información sobre la estructura del mensaje).
El modo ECB no se recomienda para cifrar datos sensibles debido a su falta de aleatorización
Cifrado ECB
#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() {}