Language: EN

encriptacion-entre-ordenador-y-esp32

Encryption between Computer and ESP32

In this article, we will explore how to implement a full and functional AES-128 encryption between two ESP32s, or with other devices.

Encryption is a fundamental technique in the development of secure systems. You simply need encryption. But, it is often ignored in almost all tutorials.

It makes sense, as it is a rather complex topic. Diving deep into it, with a complete example, is quite challenging.

However, precisely because no one discusses it, we will see an example of a functional solution on how you could implement encryption in your ESP32 project.

Additionally, we will provide code examples to perform communication (encrypting and decrypting data) in the languages Python, Node.js and C#.

This tutorial is advanced. It is highly recommended that you have read the rest of the security and encryption tutorials in the course.

Operating Scheme

We will look at a possible process of encrypting and decrypting messages using AES in CBC mode with a randomly generated IV (initialization vector). It also uses MD5 to derive a key from a given password.

That is, the encoding process is approximately as follows.

esquema

There it is

It is not a totally perfect process (there are a few things that could be improved) but in general, it is a pretty good example of how encryption would work in a “real” project.

Now let’s see how to implement it 👇.

C++ Code on the ESP32

First, let’s look at the C++ example for the ESP32. More or less, these are the pieces we have seen in the rest of the tutorials, together.

Here is the complete code for encryption and decryption using CBC AES-128 on the ESP32, separated into files for your convenience 😉

#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==";

Code in Different Languages

In addition to communication between ESP32s, we typically want to communicate the ESP32 with other types of devices *(with your computer, or your mobile, or a server, etc).

So we will have to implement the same encryption and decryption process to make the communication secure.

Here is the code ported to different languages.

Python Code


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# Code

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));
            }
        }
    }

Node.js Code

const crypto = require("crypto");

function createKey(password) {
    return crypto.createHash("sha256").update(password).digest(); // Derives SHA-256 key
}

function encrypt(message, password) {
    const key = createKey(password);
    const iv = crypto.randomBytes(16); // Generates random IV

    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;
}

// 🔹 Example of use
const password = "www.luisllamas.es";
const message = "your_message";

// Encrypt message
const encrypted = encrypt(message, password);
console.log("🔐 Encrypted Message:", encrypted);

//