Skip to content

Instantly share code, notes, and snippets.

@Sv443
Last active February 20, 2025 20:16
Show Gist options
  • Save Sv443/d591abc5538b9b31b96194add0c9fd06 to your computer and use it in GitHub Desktop.
Save Sv443/d591abc5538b9b31b96194add0c9fd06 to your computer and use it in GitHub Desktop.
ESP32 Arduino - secure AES-GCM key and IV generation and storage example
#include <Preferences.h>
#include <esp_random.h>
#include <mbedtls/sha256.h>
#ifndef String
#include <Arduino.h>
#endif
// set to true to clear the NVS before generating and writing a new key and IV:
#define CLEAR_ALL false
#define AES_KEY_SIZE 16 // key size in bytes (16 for a 128-bit AES key)
#define IV_SIZE 12 // recommended IV size for AES-GCM is 12
Preferences prefs;
// Store AES Key in Encrypted NVS
void storeAESKey(const uint8_t key[AES_KEY_SIZE]) {
prefs.begin("secure", false);
prefs.putBytes("aes_key", key, AES_KEY_SIZE);
prefs.end();
Serial.println("AES key stored securely in NVS.");
}
// Load AES Key from Encrypted NVS
bool loadAESKey(uint8_t key[AES_KEY_SIZE]) {
prefs.begin("secure", false);
size_t keySize = prefs.getBytes("aes_key", key, AES_KEY_SIZE);
prefs.end();
if(keySize == AES_KEY_SIZE) {
Serial.println("AES key loaded successfully.");
return true;
} else {
Serial.println("AES key not found in NVS.");
return false;
}
}
// Generate a Secure Random AES Key
void generateRandomAESKey(uint8_t key[AES_KEY_SIZE]) {
for(int i = 0; i < AES_KEY_SIZE; i++)
key[i] = esp_random() & 0xFF;
}
// Store Counter in NVS for IV Generation
void storeIVCounter(uint32_t counter) {
prefs.begin("secure", false);
prefs.putUInt("iv_counter", counter);
prefs.end();
}
// Load Counter from NVS (or initialize to 0 if not found)
uint32_t loadIVCounter() {
prefs.begin("secure", false);
uint32_t counter = prefs.getUInt("iv_counter", 0); // Default to 0 if missing
prefs.end();
return counter;
}
// Hash the IV using SHA-256 to eliminate leading zeros
void hashIV(uint8_t iv[IV_SIZE]) {
uint8_t hash[32]; // Full SHA-256 hash output (32 bytes)
// Compute SHA-256 hash of the IV
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0); // 0 = SHA-256 (not SHA-224)
mbedtls_sha256_update(&ctx, iv, IV_SIZE);
mbedtls_sha256_finish(&ctx, hash);
mbedtls_sha256_free(&ctx);
// Use the first 12 bytes of the hash as the new IV
memcpy(iv, hash, IV_SIZE);
}
// Generate a Secure IV (Counter + Random Component)
void generateIV(uint8_t iv[IV_SIZE]) {
// Load counter from NVS
uint32_t counter = loadIVCounter();
// Convert counter to bytes (Big-Endian format)
iv[0] = (counter >> 24) & 0xFF;
iv[1] = (counter >> 16) & 0xFF;
iv[2] = (counter >> 8) & 0xFF;
iv[3] = counter & 0xFF;
// Generate remaining 8 random bytes
for(int i = 4; i < IV_SIZE; i++)
iv[i] = esp_random() & 0xFF;
// Increment and store the updated counter
storeIVCounter(counter + 1);
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n");
uint32_t u1 = micros();
#if CLEAR_ALL
// Delete AES Key & Counter from NVS (for debugging)
prefs.begin("secure", false);
prefs.remove("aes_key");
prefs.remove("iv_counter");
prefs.end();
Serial.println("Cleared aes_key & iv_counter preferences\n\n");
#endif
uint8_t aesKey[AES_KEY_SIZE];
uint32_t u2_1 = 0;
uint32_t u2_2 = 0;
// Try to load an existing key
if(!loadAESKey(aesKey)) {
u2_1 = micros();
Serial.println("Generating a new AES key...");
generateRandomAESKey(aesKey);
u2_2 = micros();
storeAESKey(aesKey);
}
uint32_t u2 = micros();
// Generate IV
uint8_t iv[IV_SIZE];
generateIV(iv);
uint32_t u3 = micros();
// Hash the IV
hashIV(iv);
uint32_t u4 = micros();
// Print AES Key (DO NOT PRINT IN PRODUCTION!)
Serial.print("AES Key: ");
for(int i = 0; i < AES_KEY_SIZE; i++)
Serial.printf("%02X ", aesKey[i]);
Serial.println();
// Print IV
Serial.print("Generated IV: ");
for(int i = 0; i < IV_SIZE; i++)
Serial.printf("%02X ", iv[i]);
Serial.println();
uint32_t u5 = micros();
Serial.println("\nTimings (ms):");
Serial.println(" AES Key:");
if(u2_1 > 0) {
Serial.print(" - READ: ");
Serial.println(String(1.0 * (u2_1 - u1) / 1000));
Serial.print(" - GENER8: ");
Serial.println(String(1.0 * (u2_2 - u2_1) / 1000));
Serial.print(" - WRITE: ");
Serial.println(String(1.0 * (u2 - u2_2) / 1000));
}
else {
Serial.print(" - READ: ");
Serial.println(String(1.0 * (u2 - u1) / 1000));
}
Serial.println(" IV:");
Serial.print(" - GENER8: ");
Serial.println(String(1.0 * (u3 - u2) / 1000));
Serial.print(" - HASH: ");
Serial.println(String(1.0 * (u4 - u3) / 1000));
Serial.println(" Print Key:");
Serial.print(" - PRINT: ");
Serial.println(String(1.0 * (u5 - u4) / 1000));
Serial.println(" Total: ");
Serial.print(" - TOTAL: ");
Serial.println(String(1.0 * (u5 - u1) / 1000));
Serial.print(" - NOPRNT: ");
Serial.println(String(1.0 * ((u5 - u1) - (u5 - u4)) / 1000));
}
void loop() {}

Using an ESP32-S3 at 240MHz (WiFi) setting, I got the following timing results - NOPRNT is the most accurate timing (excludes most debug printing)

With CLEAR_ALL set to true:

grafik


With CLEAR_ALL set to false:

grafik

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment