En este artículo, exploraremos cómo implementar una encriptación AES-128 completa y funcional entre dos ESP32, o con otros dispositivos.
La encriptación es una técnica fundamental en el desarrollo de sistemas seguros. Simplemente necesitas encriptación. Pero, normalmente se ignora en casi todos los tutoriales.
Es lógico, ya que es un tema bastante complejo. Verlo en profundidad, con un ejemplo completo, es algo durito.
Pero, precisamente porque no lo cuenta nadie, pues vamos a ver un ejemplo de una solucón funcional de cómo podrías implementar una encriptación en tu proyecto de ESP32.
Además, proporcionaremos ejemplos de código para realizar la comunicación (encriptando y desencriptando los datos) en los lenguajes Python, Node.js y C#.
Este tutorial es avanzado. Para leerlo, conviene muchísimo que hayáis leído el resto de tutoriales se seguridad e encriptación del curso.
Esquema de funcionamiento
Vamos a ver un posible proceso de cifrado y descifrado de mensajes utilizando AES en modo CBC con un IV (vector de inicialización) generado aleatoriamente. También emplea MD5 para derivar una clave a partir de una contraseña dada.
Es decir, que el proceso de codificación es aproximadamente el siguiente.
No es un proceso totalmente perfecto (hay alguna cosita que se podría mejora) pero en general es bastante buen ejemplo de cómo funcionaría una encriptación en un proyecto “real”.
Ahora vamos a ver cómo implementarlo 👇.
Código de C++ en el ESP32
Primero vamos a ver el ejemplo de C++ para el ESP32. Más o menos son las piezas que hemos visto en el resto de tutoriales, juntas.
Os dejo el código complejto de cifrado y descifrado utilizando CBC AES-128 en el ESP32, separadito en archivos para que lo tengáis más a mano 😉
#include "stdlib.h"
#include "mbedtls/md.h"
#include "mbedtls/aes.h"
#include "mbedtls/base64.h"
#include "../constants.h"
#include "../crypto.hpp"
#include "../utils.hpp"
void testEncode()
{
Serial.println("TestEncode");
std::string password(PASSWORD);
auto key = generateMD5(password);
auto iv = generateIV();
std::string message(MESSAGE);
std::vector<uint8_t> data(message.begin(), message.end());
auto padded = pkcs7_padding(data);
auto ciphered = encryptAES128(padded, iv, key);
std::string encoded = encode_base64(ciphered);
std::string encodedIV = encode_base64(iv);
printBlockAsUtf8((uint8_t*)encoded.c_str(), encoded.length());
Serial.println();
printBlockAsUtf8((uint8_t*)encodedIV.c_str(), encodedIV.length());
Serial.println(); Serial.println();
}
void testDecode()
{
Serial.println("TestDecode");
std::string password(PASSWORD);
auto key = generateMD5(password);
std::string encrypted_test(ENCRYPTED);
std::string IV_test(IV);
auto decoded = decode_base64(encrypted_test);
auto decodedIV = decode_base64(IV_test);
auto deciphered = decryptAES128(decoded, decodedIV, key);
auto unpadded = pkcs7_unpadding(deciphered);
printBlockAsUtf8(unpadded.data(), unpadded.size());
Serial.println(); Serial.println();
}
void testEncodeAndDecode()
{
Serial.println("TestEncodeAndDecode");
std::string password(PASSWORD);
auto key = generateMD5(password);
auto iv = generateIV();
std::string message(MESSAGE);
std::vector<uint8_t> data(message.begin(), message.end());
auto padded = pkcs7_padding(data);
auto ciphered = encryptAES128(padded, iv, key);
auto encoded = encode_base64(ciphered);
auto encodedIV = encode_base64(iv);
auto decoded = decode_base64(encoded);
auto decodedIV = decode_base64(encodedIV);
auto deciphered = decryptAES128(decoded, iv, key);
auto unpadded = pkcs7_unpadding(deciphered);
printBlockAsUtf8(unpadded.data(), unpadded.size());
Serial.println(); Serial.println();
}
void setup()
{
Serial.begin(115200);
delay(2000);
testEncode();
testDecode();
testEncodeAndDecode();
}
void loop()
{
delay(1000);
}
#pragma once
#include "stdlib.h"
#include "mbedtls/md.h"
#include "mbedtls/aes.h"
#include "mbedtls/base64.h"
#include "../constants.h"
#include "../utils.hpp"
std::vector<uint8_t> generateIV()
{
std::vector<uint8_t> iv(16);
auto pseudo_random = esp_random();
memcpy(iv.data(), &pseudo_random, 16);
return iv;
}
std::vector<uint8_t> generateMD5(std::string& text)
{
std::vector<uint8_t> hash(16);
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_MD5;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, (const unsigned char*)text.c_str(), text.length());
mbedtls_md_finish(&ctx, hash.data());
mbedtls_md_free(&ctx);
return hash;
}
std::string encode_base64(std::vector<uint8_t>& data)
{
size_t outputLength;
mbedtls_base64_encode(nullptr, 0, &outputLength, (unsigned char*)data.data(), data.size());
std::vector<uint8_t> encoded(outputLength);
mbedtls_base64_encode(encoded.data(), outputLength, &outputLength, (unsigned char*)data.data(), data.size());
std::string rst((char*)encoded.data(), outputLength);
return rst;
}
std::vector<uint8_t> decode_base64(std::string& text)
{
size_t outputLength;
mbedtls_base64_decode(nullptr, 0, &outputLength, (unsigned char*)text.c_str(), text.length());
std::vector<uint8_t> decoded(outputLength);
mbedtls_base64_decode(decoded.data(), outputLength, &outputLength, (unsigned char*)text.c_str(), text.length());
return decoded;
}
std::vector<uint8_t> encryptAES128(std::vector<uint8_t>& input, std::vector<uint8_t> iv, std::vector<uint8_t>& key)
{
std::vector<uint8_t> output(input.size());
mbedtls_aes_context aes_dec;
mbedtls_aes_init(&aes_dec);
mbedtls_aes_setkey_enc(&aes_dec, (unsigned char*)key.data(), key.size() * 8);
mbedtls_aes_crypt_cbc(&aes_dec, MBEDTLS_AES_ENCRYPT, input.size(), (unsigned char*)iv.data(), input.data(), output.data());
mbedtls_aes_free(&aes_dec);
return output;
}
std::vector<uint8_t> decryptAES128(std::vector<uint8_t>& input, std::vector<uint8_t> iv, std::vector<uint8_t>& key)
{
std::vector<uint8_t> output(input.size());
mbedtls_aes_context aes_dec;
mbedtls_aes_init(&aes_dec);
mbedtls_aes_setkey_enc(&aes_dec, (unsigned char*)key.data(), key.size() * 8);
mbedtls_aes_crypt_cbc(&aes_dec, MBEDTLS_AES_DECRYPT, input.size(), (unsigned char*)iv.data(), input.data(), output.data());
mbedtls_aes_free(&aes_dec);
return output;
}
std::vector<uint8_t> pkcs7_padding(std::vector<uint8_t>& input)
{
std::vector<uint8_t> output(input);
size_t padded_length = (input.size() / 16 + 1) * 16;
size_t padToAdd = padded_length - input.size();
for(auto i = 0; i < padToAdd; i++)
{
output.push_back(padToAdd);
}
return output;
}
std::vector<uint8_t> pkcs7_unpadding(std::vector<uint8_t>& input)
{
std::vector<uint8_t> output(input);
size_t padToRemove = input[input.size() - 1];
for(auto i = 0; i < padToRemove; i++)
{
output.pop_back();
}
return output;
}
void printBlockAsUtf8(std::vector<uint8_t> data)
{
for(int i = 0; i < data.size(); i++)
{
Serial.print((char)data[i]);
}
}
void printBlockAsUtf8(uint8_t* data, int length)
{
for(int i = 0; i < length; i++)
{
Serial.print((char)data[i]);
}
}
void printBlockAsHex(std::vector<uint8_t> data)
{
for(int i = 0; i < data.size(); i++)
{
Serial.printf("%02X ", data[i]);
}
}
void printBlockAsHex(uint8_t* data, int length)
{
for(int i = 0; i < length; i++)
{
Serial.printf("%02X ", (int)data[i]);
}
}
String CharArrayToString(const uint8_t* data, int len)
{
String rst;
rst.reserve(len);
for(auto i = 0; i < len; i++)
{
rst += (char)data[i];
}
return rst;
}
#pragma once
char PASSWORD[] = "www.luisllamas.es";
char MESSAGE[] = "{\"data\":[{\"status\":\"off\",\"value\":15000,\"id\":0},{\"status\":\"off\",\"value\":1000,\"id\":1},{\"status\":\"off\",\"value\":1000,\"id\":2}]}";
char ENCRYPTED[] = "XYfP4278sCpGQJBl/mob4H8kMW4LravrWrBkJdwmO8cNrF3sk2h3s1iKKxTNafUwXu4CPf53thc6Ra0CbAvQh6L16z0jEiSFUKvF8QdU6Nbp/+WAohgwXJYYGATtPciBoGQo+NHHB9V0ikd7q4Zru7DBSXBCtCbWAdvyhTLN/ow=";
char IV[] = "osyaPtRhYYcTKaHWIKX7xA==";
Código en diferentes lenguajes
Además de entre comunicación entre ESP32, lo normal es que queramos comunicar el ESP32 con otro tipo de dispositivos *(con tu ordenador, o tu móvil, o un servidor, etc).
Así que vamos a tener que implementar el mismo proceso de encriptación y desencriptación para poder hacer la comunicación segura.
Aquí os dejo el código portado a distintos lenguajes.
Código en Python
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random
def r_pad(payload, block_size=16):
length = block_size - (len(payload) % block_size)
return payload + chr(length) * length
def encrypt(message, password):
key = hashlib.sha256(password.encode()).digest()
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(r_pad(message).encode())
return (base64.b64encode(iv), base64.b64encode(encrypted))
def decrypt(data, password, iv):
key = hashlib.sha256(password.encode()).digest()
IV = base64.b64decode(iv)
cipher = AES.new(key, AES.MODE_CBC, IV)
data = base64.b64decode(data)
uncrypted = cipher.decrypt(data)
return uncrypted
key = "www.luisllamas.es"; ;
message = "your_message";
encrypted = "your_encrypted";
iv = "your_iv";
(iv, ciphertext) = encrypt(message, password)
print(decrypt(encrypted, password, iv))
Código en C#
var key = "www.luisllamas.es"; ;
var message = "your_message";
var encrypted = "your_encrypted";
var iv = "your_iv";
var message_encrypted = CryptoHelper.Encrypt(message, key);
Console.WriteLine("Encrypt test");
Console.WriteLine(message_encrypted);
var encrypted_decrypted = CryptoHelper.Decrypt(encrypted, key, iv);
Console.WriteLine("\nDecrypt test");
Console.WriteLine(encrypted_decrypted);
var message_descrypted = CryptoHelper.Decrypt(message_encrypted.data, key, message_encrypted.iv);
Console.WriteLine("\nEncrypt-decrypt test");
Console.WriteLine(message_descrypted);
Console.ReadLine();
public static class CryptoHelper
{
internal static (string data, string iv) Encrypt(string message, string password)
{
byte[] data = Encoding.UTF8.GetBytes(message);
byte[] key = CreateKey(password);
var cipher = Encrypt(data, key);
var encrypted = Convert.ToBase64String(cipher.data);
var iv = Convert.ToBase64String(cipher.iv.ToArray());
return (encrypted, iv);
}
internal static (byte[] data, byte[] iv) Encrypt(byte[] data, byte[] key)
{
byte[] IV;
byte[] encrypted;
using (var aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.GenerateIV();
IV = aesAlg.IV;
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
}
encrypted = memoryStream.ToArray();
}
}
return (encrypted, IV);
}
internal static string Decrypt(string message, string password, string iv)
{
byte[] data = Convert.FromBase64String(message);
byte[] key = CreateKey(password);
byte[] IV = Convert.FromBase64String(iv);
string decrypted = Decrypt(data, key, IV);
return decrypted;
}
internal static string Decrypt(byte[] message, byte[] key, byte[] iv)
{
string decrypted;
using (var aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.IV = iv;
var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (var memoryStream = new MemoryStream(message))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (var streamReader = new StreamReader(cryptoStream))
{
decrypted = streamReader.ReadToEnd();
}
}
}
}
return decrypted;
}
private static byte[] CreateKey(string input)
{
using (MD5 hashCreator = MD5.Create())
{
return hashCreator.ComputeHash(Encoding.UTF8.GetBytes(input));
}
}
}
Código en Node.js
const crypto = require("crypto");
function createKey(password) {
return crypto.createHash("sha256").update(password).digest(); // Deriva clave SHA-256
}
function encrypt(message, password) {
const key = createKey(password);
const iv = crypto.randomBytes(16); // Genera IV aleatorio
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
let encrypted = cipher.update(message, "utf8", "base64");
encrypted += cipher.final("base64");
return {
data: encrypted,
iv: iv.toString("base64"),
};
}
function decrypt(encryptedData, password, ivBase64) {
const key = createKey(password);
const iv = Buffer.from(ivBase64, "base64");
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
let decrypted = decipher.update(encryptedData, "base64", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
// 🔹 Ejemplo de uso
const password = "www.luisllamas.es";
const message = "your_message";
// Cifrar mensaje
const encrypted = encrypt(message, password);
console.log("🔐 Mensaje Cifrado:", encrypted);
// Descifrar mensaje
const decrypted = decrypt(encrypted.data, password, encrypted.iv);
console.log("🔓 Mensaje Descifrado:", decrypted);