Common Mistakes With AES on Microcontrollers

AES itself is not the problem. The cipher is sound and has resisted decades of analysis. What breaks encryption on embedded devices is how AES gets used: the mode, the IV, the key, the randomness, and the assumptions around them. These are the mistakes I find most often, and each one quietly defeats the protection while the code appears to work.
The Cipher Is Fine, the Usage Is Not
When an assessment finds broken encryption on a device, the flaw is almost never in AES. It is in the dozen decisions that surround the cipher: which mode, how the IV is chosen, where the key comes from, whether integrity is checked. AES turns a key and a block into ciphertext correctly every time; the failures live in everything else.
That is good news and bad news. Good, because you do not need to replace the cipher. Bad, because the mistakes are easy to introduce by reaching for a library default and they do not announce themselves. The code compiles, the data comes back decrypted, and the device ships with encryption that an attacker walks straight through.
ECB Mode Leaks Structure
The simplest mode, ECB, encrypts identical plaintext blocks to identical ciphertext blocks. That means any structure in the data, repeated values, patterns, fixed headers, survives encryption as repeated patterns in the ciphertext. It is the mode a library hands you if you call the raw block function in a loop.
// the mistake: encrypting block by block in ECB
for (i = 0; i < len; i += 16)
aes_encrypt(key, plaintext + i, ciphertext + i); // ECB, leaks patternsThe classic demonstration is encrypting an image in ECB and still seeing the picture in the ciphertext, because identical regions encrypt identically. On a device, ECB over structured telemetry or configuration leaks far more than anyone intends. Reaching for it because it is the simplest call is a visible, recognizable mistake.
Reused or Static IVs
Modes like CBC and CTR need a unique initialization vector per message. The IV does not have to be secret, but it must not repeat under the same key. A fixed IV compiled into the firmware, or one that resets to zero each boot, undermines confidentiality and, in CTR mode, can expose the key stream and the data outright.
// the mistake: a hardcoded IV reused for every message
uint8_t iv[16] = {0}; // same IV every time
aes_cbc_encrypt(key, iv, pt, ct, len);two messages with the same first block now produce the same first ciphertext block -> an observer learns when payloads repeat, and CTR-mode reuse can leak plaintext via xor of two ciphertexts
Hardcoded and reused IVs are one of the most common embedded crypto mistakes, because generating a fresh one each time takes a random source the device may not have set up. The fix is a unique IV per message from real randomness, stored or transmitted alongside the ciphertext where the receiver can read it.
No Integrity Check
Encryption hides data, it does not detect tampering. With a plain mode like CBC or CTR and no separate check, an attacker can flip bits in the ciphertext and cause controlled changes in the decrypted plaintext, or splice messages together, without ever knowing the key. The device decrypts the tampered data and acts on it.
// the mistake: encrypt-only, no authentication aes_ctr_encrypt(key, iv, pt, ct, len); // confidentiality only // an attacker who flips a ciphertext bit flips the same plaintext bit
This is why bit-flipping attacks work against unauthenticated encryption, and why a command channel protected with CTR alone can be manipulated into issuing different commands. Encryption without integrity solves half the problem and leaves the other half wide open.
Reach for Authenticated Encryption
The fix for the integrity gap is an authenticated mode that provides confidentiality and integrity together. AES-GCM is the common choice: it encrypts and produces an authentication tag, and decryption fails if the ciphertext or the associated data was altered. One primitive, both properties.
// the fix: authenticated encryption with AES-GCM aes_gcm_encrypt(key, iv, aad, pt, ct, tag, len); // receiver: aes_gcm_decrypt(...) returns FAIL if tag mismatch -> reject
tampered ciphertext -> tag verification fails -> decryption rejected # the device never acts on data an attacker modified
Choosing AES-GCM, or another authenticated mode, by default avoids the common pattern of encrypting without any integrity check and assuming the job is done. It also handles the IV discipline as part of its contract, since GCM nonce reuse is catastrophic and the documentation makes that requirement loud.
Nonce Reuse in GCM Is Catastrophic
Authenticated modes are not a free pass. AES-GCM in particular fails badly if a nonce is reused under the same key: an attacker who sees two messages with the same nonce can recover the authentication subkey and forge messages. The same randomness discipline that mattered for CBC matters even more here.
On a constrained device that struggles to generate unique nonces, a counter-based nonce kept in non-volatile storage is often safer than a random one, as long as it never repeats and never rolls back. The point is that switching to GCM does not remove the IV problem, it changes its shape, and the nonce still has to be unique for every message under a given key.
Keys From Weak Randomness
An AES key is only as strong as the randomness that produced it. A key derived from a predictable seed, a boot timer, a fixed value, or a poorly seeded PRNG, is guessable no matter how many bits it nominally has. A 256-bit key from a 16-bit seed has 16 bits of real strength.
// the mistake: keying from a predictable source srand(boot_time_ms()); // low-entropy, somewhat predictable seed for (i=0;i<32;i++) key[i]=rand(); // "256-bit" key, far less real entropy
Embedded devices are notoriously poor sources of randomness at boot, when they have no entropy yet, which is exactly when key generation often happens. Use a hardware random number generator if the part has one, gather entropy before generating keys, and never key from timers or counters.
Test the Randomness You Rely On
Because weak randomness silently weakens every key, it is worth testing the output of a device’s random source rather than trusting it. Capturing a large sample and running it through a statistical test suite reveals bias and patterns that betray a poor generator.
# collect RNG output from the device and test it for bias
dieharder -a -f device_rng_sample.bin | grep -iE 'WEAK|FAILED'diehard_birthdays WEAK rgb_lagged_sum FAILED # the device's "random" output is biased -> keys are weaker than their bit length
A random source that fails statistical tests is producing keys an attacker can attack far below their nominal strength. Catching that on the bench, before the keys protect anything in the field, is the difference between a 256-bit key and a 256-bit label on a guessable value.
Key Reuse Across Purposes
Another quiet mistake is using one key for everything, encrypting data, authenticating messages, deriving session keys, because managing several keys is more work. Reusing a key across purposes can create interactions that weaken all of them, and it certainly means one compromise loses everything at once.
The clean pattern is one key per purpose, derived from a master secret with a proper key derivation function so the keys are independent. On a small device that feels heavy, but a single KDF call per purpose at startup is cheap, and it keeps the encryption key, the MAC key, and any session keys cryptographically separated.
Rolling Your Own Construction
Teams sometimes assemble their own scheme from AES plus a hash plus some glue, and the glue is where it breaks. Encrypt-and-MAC versus encrypt-then-MAC ordering, a MAC that does not cover the IV, a length field left unauthenticated: these subtle construction errors defeat otherwise sound primitives.
The reliable move is to use a vetted authenticated mode or a vetted library construction rather than composing one. AES-GCM and ChaCha20-Poly1305 exist precisely so that you do not have to get the composition right yourself. When I see a hand-rolled encrypt-and-authenticate scheme, I read it carefully, because that glue is where the real bugs usually are.
Library Defaults Are Not Always Safe
The convenience path, calling a crypto library with its defaults, is safer than it used to be but still not automatic. Some libraries default to ECB for the raw cipher object, some require you to pick the mode, and some make nonce management your responsibility with little warning. The default is a starting point, not a guarantee.
Reading the library’s documentation for the specific function you call, and confirming the mode, the IV handling, and whether the output is authenticated, takes minutes and catches the most common mistakes. The cipher is well implemented in any reputable library; the question is whether you invoked it in a safe configuration.
Getting the Basics Right
Most broken embedded encryption comes down to a handful of avoidable usage mistakes: ECB mode that leaks structure, a reused or hardcoded IV, no integrity check, keys from weak randomness, and a key reused across purposes. Each is easy to introduce and each undermines the protection while the code looks correct.
The reliable approach is short to state: use an authenticated mode like AES-GCM, generate a fresh unique IV or nonce for every message from a real random source, seed keys from hardware randomness, and keep keys separated by purpose. Get those right and AES does its job. Get them wrong and the strongest cipher in the world protects nothing.
What I Check in a Review
When I review embedded crypto, I am rarely looking for a flaw in AES itself. I am checking the mode, the IV or nonce handling, whether integrity is verified, and where the key and the randomness come from. Those four areas account for the overwhelming majority of real-world failures I find.
That focus makes the review efficient and the findings actionable. A report that says the cipher is broken is rare and dramatic; a report that says you are using CBC with a hardcoded IV and no MAC is common and fixable, and it points the team straight at the change that turns decorative encryption into real protection.
Where This Fits
Reviewing how a device actually uses cryptography, not whether the algorithm is strong but whether the usage is sound, is a core part of a product security assessment. If you want your embedded crypto usage reviewed for these failure modes, that is the kind of work we do at Berkner Tech.