June 16, 2026
Giving Your ESP32 a Hardware Conscience: Secure IoT with the ATECC608A
Why your IoT device’s secret keys should never live in firmware and how a $1 chip and an ESP32 fix that, AES-256 included.
Rusiru
7 min read
Why your IoT device's secret keys should never live in firmware and how a $1 chip and an ESP32 fix that, AES-256 included.
TL;DR_ The ESP32 is a fantastic IoT brain, but it stores its secrets in flash, where anyone with the board can read them. The Microchip ATECC608A is a tamper-resistant secure element that generates and keeps private keys in silicon they can_ never leave. Pair the two over I²C and you get a hardware root of trust. And here's the neat part: the ATECC608A only does AES-128, but you don't need more from it the ESP32's own hardware AES accelerator handles AES-256, so each chip does what it's best at.
The problem nobody likes to talk about
You built an IoT product on an ESP32. It talks to the cloud over TLS, maybe AWS IoT or Azure. To do that, it needs a private key and a certificate. So where do you put them?
Almost everyone's first answer is: in flash. A header file, NVS, SPIFFS, somewhere on the chip. And that works right up until someone buys your device, pops off the case, clips onto the flash, and dumps it. Now they have your private key. They can clone your device, impersonate it to your backend, push fake telemetry, or pull down firmware updates meant only for legitimate hardware.
Software-based key storage has a fundamental ceiling: if the CPU can read the key, so can an attacker who controls the CPU. No amount of obfuscation changes that.
The fix is to make sure the key is never readable by anyone not your firmware, not an attacker, not even you. That requires hardware.
Enter the ATECC608A
The ATECC608A is a secure element from Microchip's CryptoAuthentication™ family. It's a tiny 8-pin chip that costs roughly the price of a coffee in volume, and its entire job is to be a vault.
The defining feature is simple but powerful: private keys are generated inside the chip and physically cannot be exported. You can ask the chip to use a key to sign something, to do a key exchange but you can never ask it to hand the key over. There's no command for that. The secret lives and dies inside tamper-resistant silicon.
On top of that vault, it gives you real cryptographic horsepower in hardware:
Capability What the ATECC608A provides Asymmetric crypto ECC on the NIST P-256 curve ECDSA sign/verify, ECDH key agreement Hashing SHA-256, HMAC, plus PRF/HKDF for TLS key derivation Symmetric AES-128 only (ECB, with Galois-field support for GCM) Randomness A true hardware random number generator (TRNG) Storage 16 slots for keys, certificates, and secret data Secure boot ECDSA validation of your firmware signature
That AES-128 line is the one that trips people up and it's where the ESP32 partnership gets interesting.
"But I need AES-256!"
This is the question that sent me down this rabbit hole in the first place. The ATECC608A's hardware AES engine is AES-128 only. There's no AES-256 mode in the silicon. So if your spec calls for AES-256, is the chip useless to you?
Not at all and the reason is that you were never supposed to run bulk AES on the secure element anyway.
The ESP32 has its own hardware AES accelerator, and unlike the ATECC608A, it supports 128, 192, and 256-bit keys. It's fast, it's built into the SoC, and mbedTLS (which ships with ESP-IDF) uses it automatically. So the right division of labor is:
- ATECC608A → the trust layer. It guards the long-lived private key, proves the device's identity (ECDSA), performs the key agreement (ECDH), derives session keys (HKDF), and feeds you good randomness.
- ESP32 → the throughput layer. It runs AES-256 on its hardware accelerator to encrypt and decrypt the actual data stream.
Think of it like a bank. The ATECC608A is the vault and the ID-verification desk small, paranoid, never lets the master key out of its sight. The ESP32 is the high-speed cash counter. You don't count a million notes inside the vault; you count them at the counter, using a key the vault handed you for this one session.
A typical secure session looks like this:
- The ATECC608A and the cloud authenticate each other with ECDSA (signatures the secure element makes with its non-exportable private key).
- They run ECDH to agree on a shared secret computed inside the chip, never sent over the wire.
- That shared secret is run through HKDF to produce a fresh 256-bit session key.
- The session key is handed to the ESP32's hardware AES-256 engine, which encrypts all subsequent traffic with, say, AES-256-GCM.
The master private key never leaves the vault. The session key is ephemeral. And the bulk crypto runs on the fastest engine available. Everybody wins.
Wiring it up
Physically, this is almost anticlimactic. The ATECC608A speaks I²C (it can also do a single-wire interface, but I²C is what everyone uses with the ESP32). Four wires:
ATECC608A ESP32 SDA GPIO 21 (default) SCL GPIO 22 (default) VCC 3.3V GND GND
Pins 21/22 are the defaults baked into Espressif's tooling, so unless you have a reason to move them, stick with them. If you'd rather not solder, modules like the ESP32-WROOM-32SE ship with an ATECC608A already on board, and breakout boards (e.g., SparkFun's Qwiic version) make breadboarding trivial.
The software side
Microchip maintains cryptoauthlib, the canonical driver for these chips, and crucially Espressif maintains an official fork, esp-cryptoauthlib, that drops straight into ESP-IDF as a component. It's supported on the ESP32, ESP32-S3, C3, C5, and C6, and needs ESP-IDF v5.0 or newer.
Step 1: Talk to the chip
Before anything else, initialize the library and confirm the ESP32 can see the secure element on the bus:
#include "cryptoauthlib.h"
ATCAIfaceCfg cfg = cfg_ateccx08a_i2c_default; // sane I2C defaults
if (atcab_init(&cfg) != ATCA_SUCCESS) {
printf("Could not find ATECC608A on I2C bus\n");
return;
}
printf("Secure element online.\n");#include "cryptoauthlib.h"
ATCAIfaceCfg cfg = cfg_ateccx08a_i2c_default; // sane I2C defaults
if (atcab_init(&cfg) != ATCA_SUCCESS) {
printf("Could not find ATECC608A on I2C bus\n");
return;
}
printf("Secure element online.\n");Step 2: Provision it (once)
Provisioning is where you generate the device's key pair and lock down the configuration. The private key is born inside the chip during this step and never comes out you only ever receive the public key.
uint8_t public_key[64];
// Generate a brand-new P-256 key pair in slot 0.
// The private half stays in the chip forever.
atcab_genkey(0, public_key);uint8_t public_key[64];
// Generate a brand-new P-256 key pair in slot 0.
// The private half stays in the chip forever.
atcab_genkey(0, public_key);Espressif even ships a helper, esp_cryptoauth_utility (a Python script, secure_cert_mfg.py), that walks you through generating the key, building a certificate signing request, and writing certificates handy when you're onboarding to AWS IoT or Azure DPS. One important gotcha: once you lock the configuration zone, it's permanent. Test your config thoroughly before you commit, because there's no undo.
Step 3: Prove the device is genuine
Authentication is a challenge–response dance. The server sends a random challenge; the chip signs it with the private key only it possesses; the server verifies with the public key. A valid signature is mathematical proof the device is the real thing.
uint8_t challenge[32]; // random nonce from the server
uint8_t signature[64];
// Sign the challenge using the private key in slot 0.
// The key is used, but never revealed.
atcab_sign(0, challenge, signature);
// ...send `signature` back to the server for verification.uint8_t challenge[32]; // random nonce from the server
uint8_t signature[64];
// Sign the challenge using the private key in slot 0.
// The key is used, but never revealed.
atcab_sign(0, challenge, signature);
// ...send `signature` back to the server for verification.Step 4: Derive a session key and hand it to the ESP32
After ECDH and HKDF (both of which the secure element can do), you've got a 256-bit session key. Now let the ESP32's hardware do the heavy lifting with AES-256-GCM via mbedTLS:
#include "mbedtls/gcm.h"
mbedtls_gcm_context gcm;
mbedtls_gcm_init(&gcm);
// 256-bit key derived via the secure element — ESP32 HW AES kicks in here
mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, session_key, 256);
mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT,
plaintext_len,
iv, sizeof(iv),
aad, aad_len,
plaintext, ciphertext,
16, tag); // 16-byte auth tag
mbedtls_gcm_free(&gcm);#include "mbedtls/gcm.h"
mbedtls_gcm_context gcm;
mbedtls_gcm_init(&gcm);
// 256-bit key derived via the secure element — ESP32 HW AES kicks in here
mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, session_key, 256);
mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT,
plaintext_len,
iv, sizeof(iv),
aad, aad_len,
plaintext, ciphertext,
16, tag); // 16-byte auth tag
mbedtls_gcm_free(&gcm);No special flags needed on the ESP32, mbedTLS routes AES through the hardware accelerator automatically.
Does it actually help performance?
It does, and not just security. The wolfSSL team benchmarked a full TLS 1.3 handshake on an ESP32-WROOM-32SE and measured roughly:
- ~2040 ms pure software crypto
- ~997 ms with the ESP32's hardware SHA/AES/RSA acceleration
- ~765 ms with the ATECC608A handling the ECC operations
So offloading the asymmetric math to the secure element didn't just lock down the keys it nearly halved the handshake time versus software, and shaved more off the hardware-accelerated baseline. Security and speed, same upgrade.
Where this shows up in the real world
This ESP32 + ATECC608A pattern is everywhere once you start looking:
- Cloud onboarding AWS IoT and Azure IoT (via DPS) both work beautifully with secure-element-stored credentials, and the provisioning tools are built around exactly this combo.
- Secure OTA updates verify firmware signatures with the chip's ECDSA before flashing, so a device only accepts updates from you.
- Device identity at scale every unit gets a unique, unclonable identity, so a compromised single device can't masquerade as the whole fleet.
- Smart home, wearables, industrial sensors anywhere a cheap connected device needs to prove it's authentic.
The honest caveats
No chip is magic. A few things to keep in mind:
- Provisioning is the hard part. The cryptography is the easy 20%; securely injecting keys and managing certificates across a product's lifecycle is the other 80%. Budget for it.
- Locking is forever. Misconfigure the config zone and lock it, and that chip is scrap. Prototype with a part you're willing to throw away.
- Physical attacks still exist. Secure elements raise the bar enormously against software and remote attacks, but a determined adversary with lab equipment and physical access (side-channel analysis, fault injection) is a different threat model. For most IoT products, that's an acceptable trade but know where the line is.
- Don't misuse the chip's AES-128-ECB. It's meant for tiny protected secrets, not bulk data ECB leaks patterns. For real payloads, use the ESP32's AES-256-GCM as described above.
A note for new designs
If you're starting fresh today, reach for the ATECC608B instead of the 608A. It's the security-enhanced successor, it's fully backward-compatible (same commands, same slots, same code), and Microchip recommends it for all new designs. Everything in this article applies unchanged.
Wrapping up
The ESP32 gave hobbyists and product teams a ridiculously capable, cheap IoT platform. The one thing it couldn't offer was a place to keep a secret that even its own firmware couldn't betray. The ATECC608A fills that gap for about a dollar.
Wire them together over four pins, let the secure element be the paranoid vault and the ESP32 be the fast workhorse AES-256 and all and you've turned a fun dev board into something you can actually ship and defend. Your device finally has a conscience it can't be talked out of.
If you build something with this, the Espressif esp-cryptoauthlib repo and Microchip's Trust Platform are the two places to start. Happy (secure) hacking.