I underestimated cryptography on embedded systems for a long time — I figured “it’s just math, any CPU can do math.” Then I tried running full RSA key generation on an 8-bit AVR with 2KB of RAM and quickly learned why embedded cryptography is its own specialized discipline, with real constraints around processing power, memory, power consumption, and even physical security against attackers with a soldering iron and an oscilloscope. In this article, I’ll go through how embedded systems actually handle encryption and decryption, from resource-constrained software implementations up through dedicated hardware crypto accelerators and secure elements.
Why Encryption Matters in Embedded Systems
Embedded devices increasingly handle sensitive data and critical functions — IoT sensors reporting to the cloud, medical devices transmitting patient data, vehicles receiving over-the-air updates, industrial controllers accepting remote commands. Without encryption, this data and these commands can be intercepted, read, or forged by anyone with access to the communication channel.
flowchart TB
A[Why Embedded Encryption Matters] --> B[Data Confidentiality<br/>Protect sensitive data in transit]
A --> C[Authentication<br/>Verify sender identity]
A --> D[Integrity<br/>Detect tampering]
A --> E[Firmware/Update Protection<br/>Prevent malicious code injection]
A --> F[Access Control<br/>Protect device configuration/secrets]
The Core Challenge: Resource Constraints
Unlike a server or desktop with gigabytes of RAM and multi-core processors running at gigahertz speeds, a typical embedded MCU might have only tens of kilobytes of RAM, run at tens of megahertz, and need to complete cryptographic operations within a strict power budget (especially for battery-powered devices). This fundamentally shapes which cryptographic algorithms and implementation strategies are practical.
| Constraint | Impact on Cryptography |
|---|---|
| Limited RAM | Restricts key sizes, buffer sizes for block operations |
| Limited CPU speed | Makes computationally heavy algorithms (RSA) slow without hardware acceleration |
| Limited flash/code size | Favors compact, well-optimized crypto libraries over full-featured ones |
| Battery power budget | Favors algorithms with lower energy-per-operation (hardware acceleration especially helps here) |
| Real-time requirements | Crypto operations must not block time-critical tasks for too long |
Symmetric Encryption: AES
Symmetric encryption uses the same key for both encryption and decryption, and is generally far more computationally efficient than asymmetric encryption — making it the preferred choice for encrypting bulk data on resource-constrained devices. AES (Advanced Encryption Standard) is by far the most widely used symmetric algorithm in embedded systems.
flowchart LR
PT[Plaintext Data] --> AES_ENC[AES Encryption<br/>with Shared Secret Key]
AES_ENC --> CT[Ciphertext]
CT -->|Transmitted over Network| CT2[Ciphertext Received]
CT2 --> AES_DEC[AES Decryption<br/>with Same Shared Key]
AES_DEC --> PT2[Original Plaintext Recovered]
// Example: AES-128 CBC encryption using mbedTLS on an embedded target
#include "mbedtls/aes.h"
Status_t encrypt_sensor_payload(uint8_t *plaintext, size_t len,
uint8_t *key, uint8_t *iv, uint8_t *ciphertext) {
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
if (mbedtls_aes_setkey_enc(&aes, key, 128) != 0) {
mbedtls_aes_free(&aes);
return STATUS_ERROR_HARDWARE_FAULT;
}
// CBC mode requires data to be a multiple of block size (16 bytes) - pad if needed
if (mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, len, iv,
plaintext, ciphertext) != 0) {
mbedtls_aes_free(&aes);
return STATUS_ERROR_HARDWARE_FAULT;
}
mbedtls_aes_free(&aes);
return STATUS_OK;
}
Many modern microcontrollers (STM32 with the CRYP peripheral, ESP32 with hardware AES) include dedicated hardware AES accelerators that perform encryption/decryption directly in silicon, dramatically faster and more power-efficient than a software implementation.
// STM32 HAL example: Hardware-accelerated AES using the CRYP peripheral
CRYP_HandleTypeDef hcryp;
Status_t hw_aes_encrypt(uint8_t *plaintext, uint8_t *ciphertext, uint32_t len) {
hcryp.Instance = CRYP;
hcryp.Init.DataType = CRYP_DATATYPE_8B;
hcryp.Init.KeySize = CRYP_KEYSIZE_128B;
hcryp.Init.pKey = (uint32_t*)aes_key;
hcryp.Init.Algorithm = CRYP_AES_CBC;
hcryp.Init.pInitVect = (uint32_t*)aes_iv;
HAL_CRYP_Init(&hcryp);
if (HAL_CRYP_Encrypt(&hcryp, (uint32_t*)plaintext, len,
(uint32_t*)ciphertext, 100) != HAL_OK) {
return STATUS_ERROR_HARDWARE_FAULT;
}
return STATUS_OK;
}
// Hardware acceleration here can be 10-50x faster than a software AES loop,
// and typically consumes far less energy per byte encrypted
Asymmetric Encryption: RSA and ECC
Asymmetric (public-key) cryptography uses a key pair — a public key for encryption/verification and a private key for decryption/signing — solving the key distribution problem that symmetric encryption alone can’t handle (how do two devices securely agree on a shared secret key in the first place?). The trade-off is that asymmetric operations are significantly more computationally expensive.
sequenceDiagram
participant Device as IoT Device
participant Server as Cloud Server
Device->>Server: Request connection
Server->>Device: Send public key certificate
Device->>Device: Generate random symmetric session key
Device->>Server: Encrypt session key with server's public key, send
Server->>Server: Decrypt session key using private key
Note over Device,Server: Both now share the symmetric session key
Device->>Server: Encrypted data using fast symmetric AES
Server->>Device: Encrypted response using fast symmetric AES
This hybrid pattern — using asymmetric cryptography only briefly to establish a shared secret, then switching to fast symmetric encryption for the actual data — is exactly how TLS works, and it’s the standard approach for embedded systems too, since it minimizes the amount of expensive asymmetric computation required.
ECC (Elliptic Curve Cryptography) has become the preferred asymmetric algorithm for embedded systems over RSA, because it achieves equivalent security with much smaller key sizes (a 256-bit ECC key offers roughly the same security as a 3072-bit RSA key), directly translating to less computation, less RAM, and less energy consumption — all critical on constrained devices.
// Example: ECDSA signature verification for a received firmware update
#include "mbedtls/ecdsa.h"
Status_t verify_ecdsa_signature(uint8_t *hash, uint8_t *signature,
mbedtls_ecp_keypair *public_key) {
if (mbedtls_ecdsa_read_signature(public_key, hash, 32,
signature, signature_len) != 0) {
return STATUS_ERROR_INVALID_PARAM; // Signature invalid - reject
}
return STATUS_OK;
}
Hardware Security Modules and Secure Elements
For applications where key material must be strongly protected (payment devices, automotive security modules, high-value IoT deployments), embedded systems often incorporate a dedicated secure element — a separate, tamper-resistant chip specifically designed to store cryptographic keys and perform crypto operations without ever exposing the private key to the main microcontroller’s memory space.
flowchart TB
MCU[Main Microcontroller] -->|I2C/SPI| SE[Secure Element<br/>e.g. ATECC608, SE050]
SE --> KEYSTORE[Tamper-Resistant<br/>Key Storage]
SE --> CRYPTOENGINE[Internal Crypto Engine<br/>AES/ECC/SHA]
MCU -->|Send data to sign/encrypt| SE
SE -->|Return signed/encrypted result| MCU
KEYSTORE -.->|Private keys never leave| SE
// Example: Using a secure element (e.g., Microchip ATECC608) for signing
// The private key never leaves the secure element chip
Status_t secure_element_sign(uint8_t *hash, uint8_t *signature_out) {
ATCA_STATUS status = atcab_sign(PRIVATE_KEY_SLOT, hash, signature_out);
if (status != ATCA_SUCCESS) {
return STATUS_ERROR_HARDWARE_FAULT;
}
return STATUS_OK;
}
I reach for a secure element whenever a design needs to protect against physical attacks — a determined attacker with hardware access can potentially extract keys from a general-purpose MCU’s flash memory (through fault injection, side-channel analysis, or debug port exploitation), but a well-designed secure element is specifically hardened against exactly these attack techniques.
Hashing and Message Authentication
Beyond encryption itself, embedded systems rely heavily on cryptographic hash functions (SHA-256 being the most common) and HMAC (Hash-based Message Authentication Code) for verifying data integrity and authenticity without necessarily encrypting the data itself.
// Example: HMAC-SHA256 to authenticate a sensor data packet
#include "mbedtls/md.h"
Status_t generate_hmac(uint8_t *data, size_t len, uint8_t *key, size_t key_len,
uint8_t *hmac_out) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (mbedtls_md_hmac(md_info, key, key_len, data, len, hmac_out) != 0) {
return STATUS_ERROR_HARDWARE_FAULT;
}
return STATUS_OK;
}
// Receiver side: verify the packet hasn't been tampered with
uint8_t verify_packet_authenticity(uint8_t *data, size_t len,
uint8_t *received_hmac, uint8_t *key) {
uint8_t calculated_hmac[32];
generate_hmac(data, len, key, 32, calculated_hmac);
return (memcmp(calculated_hmac, received_hmac, 32) == 0);
}
True Random Number Generation
A frequently overlooked but critical piece of embedded cryptography is generating genuinely random numbers for keys, initialization vectors (IVs), and nonces. A predictable “random” number generator completely undermines otherwise strong cryptography — this is why most secure MCUs include a hardware True Random Number Generator (TRNG) based on physical entropy sources like thermal noise, rather than relying on a software pseudo-random number generator seeded from a predictable value like the system clock.
// STM32 HAL example: Reading from the hardware TRNG peripheral
RNG_HandleTypeDef hrng;
Status_t generate_random_key(uint8_t *key_out, size_t len) {
for (size_t i = 0; i < len; i += 4) {
uint32_t random_word;
if (HAL_RNG_GenerateRandomNumber(&hrng, &random_word) != HAL_OK) {
return STATUS_ERROR_HARDWARE_FAULT;
}
memcpy(key_out + i, &random_word, 4);
}
return STATUS_OK;
}
Secure Communication: TLS/DTLS on Embedded Devices
For network-connected embedded systems, TLS (or DTLS for UDP-based connections) provides a standardized, well-vetted way to combine all these primitives — symmetric encryption, asymmetric key exchange, certificates, and message authentication — into a secure communication channel. Lightweight TLS libraries like mbedTLS or wolfSSL are specifically designed to run within embedded memory constraints.
sequenceDiagram
participant Device as Embedded Device
participant Broker as MQTT Broker (Cloud)
Device->>Broker: TLS ClientHello
Broker->>Device: ServerHello + Certificate
Device->>Device: Verify certificate against trusted root
Device->>Broker: Key exchange (ECDHE)
Note over Device,Broker: Symmetric session keys derived
Device->>Broker: Encrypted MQTT CONNECT (over TLS)
Broker->>Device: Encrypted MQTT CONNACK
Note over Device,Broker: All further traffic encrypted with AES-GCM
// Simplified mbedTLS TLS connection setup for an IoT device connecting to MQTT broker
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
Status_t setup_tls_connection(void) {
mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
mbedtls_ssl_conf_ca_chain(&conf, &trusted_root_cert, NULL);
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
mbedtls_ssl_setup(&ssl, &conf);
if (mbedtls_ssl_handshake(&ssl) != 0) {
return STATUS_ERROR_TIMEOUT;
}
return STATUS_OK;
}
Balancing Security and Performance
| Approach | Security Level | Performance Impact | Typical Use Case |
|---|---|---|---|
| No encryption | None | None | Non-sensitive, isolated local sensors only |
| Software AES | Good | Moderate CPU overhead | Cost-constrained devices without hardware acceleration |
| Hardware-accelerated AES | Good | Minimal overhead | Most modern MCUs (STM32, ESP32) |
| Software ECC/RSA | Good | High CPU overhead, slow key operations | Infrequent operations (e.g., firmware signing checks) |
| Secure element (ATECC608, SE050) | Very High + tamper resistance | Minimal MCU overhead (offloaded) | Payment, automotive, high-value IoT |
| Full TLS/DTLS stack | Very High | Moderate RAM/flash footprint | Cloud-connected IoT devices |
Real-World Example: Securing a Smart Lock
Consider a Bluetooth-enabled smart lock — a good example of layered embedded cryptography in practice:
- Pairing/provisioning uses ECC-based key exchange (ECDH) to establish a shared secret with the owner’s phone app, without ever transmitting a static password over the air.
- Ongoing commands (“unlock”, “lock”) are encrypted with AES-128 using the established session key, and each command includes a monotonically increasing counter or timestamp inside the encrypted payload to prevent replay attacks (an attacker simply recording and re-sending a captured “unlock” command).
- Firmware updates are signed with ECDSA, verified against a public key burned into one-time-programmable memory at manufacturing time, preventing malicious firmware installation even if an attacker gains local Bluetooth access.
- Key storage for the long-term device identity key uses a secure element rather than plain flash memory, protecting against key extraction even if an attacker physically disassembles the lock.
Performance, Reliability, and Security Considerations
- Performance: Always use hardware crypto acceleration when available — the difference between hardware and software AES on a typical MCU can be an order of magnitude in both speed and energy consumption, which matters enormously for battery-powered, frequently-communicating devices.
- Reliability: Cryptographic operations that fail (bad key, corrupted data) should fail safely and explicitly — silently proceeding with unencrypted or unverified data as a fallback defeats the entire purpose of adding cryptography in the first place.
- Security: Side-channel attacks (measuring power consumption or electromagnetic emissions during crypto operations to infer secret keys) are a real threat for embedded devices attackers can physically access — constant-time cryptographic implementations and secure elements with built-in side-channel resistance are the standard mitigation.
Frequently Asked Questions
Q: Why not just use RSA for everything since it’s well-known and widely supported? RSA requires much larger key sizes for equivalent security compared to ECC, translating directly into more RAM, more flash, slower operations, and more energy consumption — all significant costs on constrained embedded hardware, which is why ECC has become the preferred choice for new embedded designs.
Q: Does my microcontroller need a hardware crypto accelerator? It’s not strictly required — software cryptographic libraries like mbedTLS work fine on many MCUs — but for devices doing frequent encryption (streaming sensor data, maintaining a TLS connection) or running on battery power, hardware acceleration meaningfully improves both speed and energy efficiency.
Q: What’s the difference between encryption and hashing? Encryption is reversible (given the right key, ciphertext can be decrypted back to plaintext) and provides confidentiality; hashing is one-way (you can’t recover the original data from a hash) and is used to verify integrity or authenticity, often combined with a secret key as HMAC.
Q: Why do I need a secure element if my MCU already supports AES and ECC in software or hardware? A secure element specifically protects the cryptographic keys themselves from physical extraction attacks; even with hardware AES/ECC acceleration, keys stored in a general-purpose MCU’s flash memory can potentially be extracted by a sufficiently motivated attacker with physical access, which a dedicated tamper-resistant secure element is specifically designed to prevent.
Summary
Embedded systems handle encryption and decryption through a careful balance of cryptographic strength and hardware resource constraints — favoring efficient symmetric algorithms like AES for bulk data, using asymmetric cryptography like ECC sparingly for key exchange and authentication, and increasingly relying on hardware acceleration or dedicated secure elements to keep both performance and power consumption within budget. Beyond just picking algorithms, robust embedded security requires attention to true random number generation, protection against replay and side-channel attacks, and secure key storage that survives physical access attempts. As embedded devices become more connected and more central to critical functions — from smart locks to vehicles to medical devices — understanding how to implement cryptography correctly within these unique constraints has become a core, non-negotiable skill for embedded developers rather than a specialized afterthought.
References
- ARM PSA Certified Security Framework Documentation
- mbedTLS Official Documentation
- STMicroelectronics STM32 Cryptographic Library and CRYP Peripheral Documentation
- Espressif ESP32 Security Features Documentation
- Microchip ATECC608 Secure Element Datasheet
- FreeRTOS Security and Cryptography Documentation