Cryptography for Embedded Engineers: The Three Things You Actually Need to Know
You don't need to understand elliptic curve mathematics to use it correctly. You do need to understand nonce reuse, timing channels, and authenticated encryption. Those three things.

You do not need to derive elliptic curve equations to ship secure firmware. You need to avoid three failure modes that still appear in CVE advisories and pen-test reports: nonce reuse in AES-GCM, timing side channels in comparison and crypto loops, and using encryption without authentication. Everything else is delegation to vetted libraries (mbedTLS 3.5, monocypher, BearSSL) with correct integration.
1. Nonce reuse (especially AES-GCM)
AES-GCM is AEAD: confidentiality + integrity in one shot. Break the unique nonce per key rule and an attacker XORs plaintexts from ciphertext overlap—game over, no brute force.
Embedded mistakes:
- Counter stored in NVM; factory reset restores
nonce=0while cloud still has old ciphertext under same key - Random 96-bit nonce without collision check on 32-bit PRNG
- Reusing
(key, IV)pair because "we only encrypt small packets"
// WRONG — static IV
static uint8_t iv[12] = {0};
mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT,
len, iv, 12, aad, aad_len, plain, cipher, 16, tag);
// BETTER — monotonic counter in secure storage, never reset without key rotation
int gcm_encrypt_unique(const uint8_t *key, const uint8_t *plain, size_t len,
uint8_t *cipher, uint8_t *tag) {
uint64_t nonce_counter = nvm_read_monotonic();
uint8_t iv[12] = {0};
memcpy(iv + 4, &nonce_counter, 8);
nvm_write_monotonic(nonce_counter + 1);
return mbedtls_gcm_crypt_and_tag(/* ... */);
}
If you cannot guarantee nonce uniqueness, use AES-GCM-SIV or ChaCha20-Poly1305 with random nonce and record-keeping—or delegate to TLS 1.3 record layer.
Bootloader signing nonces are a different problem—see bootloader signature schemes.
2. Timing side channels
Comparison of MACs, signatures, or secret keys with early-exit memcmp leaks byte-by-byte on some platforms.
// WRONG
if (memcmp(received_mac, computed_mac, 16) == 0) ...
// USE library constant-time compare
if (mbedtls_ssl_constant_time(memcmp(received_mac, computed_mac, 16)) == 0) ...
// or sodium_memcmp, etc.
Square-and-multiply RSA implemented in bare C without blinding—timing leaks key material. Do not implement RSA. Use hardware PKA on STM32 where available, or Ed25519 verify from monocypher.
Power analysis on bare metal is out of scope for most IoT threat models until you handle payment or DRM—then you need a chip with secure element (ATECC608B, SE050).
3. Authenticated encryption only
Encrypting without MAC (AES-CBC alone, AES-CTR alone) lets attacker flip bits in plaintext—decrypt "transfer $100" to "transfer $900" with controlled ciphertext edits.
Rules:
- Use AEAD: AES-GCM, ChaCha20-Poly1305, AES-CCM (802.15.4 style)
- If legacy CBC required: Encrypt-then-MAC with HMAC-SHA256 over ciphertext, MAC key separate from enc key
- Never MAC-then-encrypt
// TLS 1.3 record layer handles this — prefer TLS over homebrew framing
// Homebrew only if:
struct packet {
uint8_t nonce[12];
uint8_t ciphertext[N];
uint8_t tag[16]; /* GCM tag — verify BEFORE parsing plaintext */
};
OTA pipelines without signature verify are encryption theater—pair with OTA bootloader design.
Library hygiene (brief)
- Pin versions in SBOM; mbedTLS 2.x → 3.x API breaks caught us once
- Disable unused ciphers at compile time—smaller attack surface, smaller flash
- Entropy source: hardware TRNG (STM32 RNG) mixed via
mbedtls_hardware_poll; neverrand() - Validate cert chain with hostname pin on device; private CAs belong in trust store, not "verify none in dev shipped to prod"
Threat model sanity
| Asset | Typical embedded control |
|---|---|
| Firmware integrity | Ed25519 signature at boot |
| OTA confidentiality | TLS 1.3 + signed images |
| Local config secrets | Flash encryption or SE, not XOR |
| Debug UART | Disable in production fuse |
What I'd do next
- Automated grep CI for banned patterns:
memcmp.*mac,AES_CBCwithout HMAC,srand(time). - Pen test scope explicitly includes nonce reset and bit-flip on UDP protocols.
- Training slide: three bullets above, yearly—engineers forget between projects.
Secure boot chain overlap
Cryptography essentials do not end at application—same rules apply to bootloader verifying manifest, TLS for OTA, and encrypting NV config. We map each asset to algorithm in a one-page matrix reviewed with security auditor annually; prevents "AES-128 enough?" debates mid-sprint.
Side channel on comparison—real example
Pen tester recovered admin token byte-by-byte from timing differential on naive string compare in UART CLI—fixed in patch release. UART disabled in production fuse now; defense in depth beats perfect compare alone.
Key rotation without bricking fleet
Rotating AES-GCM key required dual-key decrypt window—devices tried key_id 2 then key_id 1 on failure for 60 days. Same pattern as bootloader signing; document in one internal "crypto lifecycle" doc to avoid reinventing per feature.
Hardware accelerator footgun
STM32 PKA ECDSA verify fast path skipped constant-time path in early HAL example—we disabled PKA for verify until vendor confirmed side-channel profile. Speed not worth key leak on premium SKU.
RNG health check
Boot-time mbedtls_entropy_func self-test fails closed if STM32 RNG health flag set—device enters service mode UART only, no cloud connect. Prevents nonce reuse after RNG silicon fault observed once in EVT lot.
Cryptography for embedded engineers is mostly integration discipline. The math is someone else's job; the nonce, timing, and MAC rules are yours.
Manish Bookreader
Electronics enthusiast, Embedded Systems Expert, Linux/Networking programmer, and Software Engineer passionate about AI, electronics, books, and cooking.

