STM32 Tutorial: Cryptography on STM32U0 explained, learn AES-CBC encryption, hashing, MAC, and digital signatures using X-CUBE-CRYPTOLIB to build secure embedded systems.
Abstract
Modern embedded devices increasingly handle sensitive data, communicate over networks, and participate in secure infrastructures such as IoT ecosystems. These scenarios require robust cryptographic mechanisms to guarantee confidentiality, integrity, authentication, and non-repudiation.
The STM32 ecosystem provides several ways to implement cryptography, including hardware accelerators and firmware libraries. One of the most practical options is the X-CUBE-CRYPTOLIB, a firmware expansion package for STM32Cube that implements common cryptographic algorithms such as AES, SHA, RSA, and ECC.
This article introduces the cryptographic capabilities available on STM32 microcontrollers and demonstrates a practical example using the STM32U0 series. We will explore typical use cases and run the AES_CBC_EncryptDecrypt example provided in the library.
This article also begins a series dedicated to embedded security with STM32U0, where future posts will explore bootloader protection, secure firmware updates, and device life-cycle management.
1. Introduction to the Open Bootloader Middleware
As embedded systems become connected to networks and cloud infrastructures, security can no longer be considered optional. Even small microcontrollers are now responsible for protecting firmware, authenticating devices, and encrypting communication channels.
Cryptography provides the mathematical foundation for these protections. In embedded systems, cryptographic mechanisms are typically used to achieve four core goals:
- Confidentiality – protecting data from unauthorized access
- Integrity – ensuring data has not been altered
- Authentication – verifying the identity of communicating entities
- Non-repudiation – preventing denial of actions such as message signing
STM32 microcontrollers support cryptographic implementations through both hardware accelerators and software libraries. The X-CUBE-CRYPTOLIB package integrates directly with STM32Cube projects and provides implementations of widely used algorithms including:
- AES encryption
- SHA hash functions
- Message authentication codes
- RSA and ECC cryptography
These algorithms can run on multiple STM32 families, including the STM32U0, enabling secure functionality even on resource-constrained devices. We’ll start with the M0+ core, since it is much simpler. the M33 and Trust Zone will be explored later on this series.
2. Main Cryptography Use Cases on STM32
The STM32 cryptography ecosystem can be divided into several categories based on their security purpose. The following sections summarize the main cryptographic branches used in embedded systems according to the STM32 cryptography documentation.
2.1. Encryption (Confidentiality)
Encryption protects data by transforming readable information (plaintext) into an unreadable form (ciphertext). Only entities possessing the correct cryptographic key can recover the original data.
Two main encryption approaches exist:
Symmetric Encryption
- Uses the same key for encryption and decryption.
- Faster and commonly used in embedded systems.
Typical algorithms:
- AES
- ChaCha20
- DES / 3DES (legacy)
AES is widely used in STM32 applications because of its performance and security level. Example use cases:
- Secure communication between IoT nodes
- Encrypting sensitive configuration stored in flash
- Protecting data stored in external memory
Asymmetric Encryption
Asymmetric cryptography uses two keys:
- Public key → encryption or signature verification
- Private key → decryption or signature creation
Typical algorithms:
- RSA
- ECC (Elliptic Curve Cryptography)
Use cases include:
- Device authentication
- Secure firmware updates
- Key exchange protocols
2.2. Hash Functions (Data Integrity)
Hash functions generate a fixed-size digest from arbitrary input data. Even a small change in the input produces a completely different output.
Typical algorithms:
- SHA-1
- SHA-256
- SHA-512
Use cases include:
- File integrity verification
- Password storage
- Firmware integrity checks
Hash functions are one-way functions, meaning the original input cannot be reconstructed from the digest.
2.3. Message Authentication Codes (MAC)
A Message Authentication Code (MAC) ensures both data integrity and authenticity.
A MAC is calculated using:
- The message
- A shared secret key
If the key is unknown, an attacker cannot generate a valid MAC.
Typical algorithms:
- HMAC
- CMAC
Use cases include:
- Authenticating communication packets
- Secure device-to-device messaging
2.4. Digital Signatures
Digital signatures allow a device to prove the origin of a message.
They are based on asymmetric cryptography:
- Private key → create signature
- Public key → verify signature
Use cases include:
- Secure boot
- Firmware authenticity verification
- Software licensing systems
Digital signatures also provide non-repudiation, meaning the sender cannot deny generating the signature.
2.5. Random Number Generation
Secure cryptography depends on strong randomness.
Random numbers are used to generate:
- Encryption keys
- Initialization vectors
- Nonces
- Cryptographic challenges
A cryptographically secure random number generator (RNG) ensures that these values cannot be predicted.
3. Hands-On: Running AES_CBC_EncryptDecrypt on STM32U0
In this section we will run a simple example demonstrating AES encryption and decryption in CBC mode.
We will use the STM32 cryptographic firmware library distributed as X-CUBE-CRYPTOLIB.
Step 1 — Download the Cryptographic Library
Download the library from ST:
The package includes:
- cryptographic middleware
- example applications
- support for multiple toolchains
- demos for different algorithms
The library implements encryption, hashing, authentication, and digital signature algorithms compatible with many STM32 families including STM32U0.
Step 2 — Import the Example
Inside the package you will find several examples.
Navigate to:
Projects/
Crypto/
Examples/
AES_CBC_EncryptDecrypt/
Import the example into STM32CubeIDE.
Step 3 — Understanding the Demo
The demo performs two operations:
- Encrypt a plaintext buffer using AES-CBC
- Decrypt the ciphertext back to the original plaintext
Important parameters:
- AES key
- Initialization Vector (IV)
- Plaintext input buffer
Simplified flow:
Plaintext
↓
AES_CBC_Encrypt
↓
Ciphertext
↓
AES_CBC_Decrypt
↓
Recovered Plaintext
CBC mode uses the previous ciphertext block to influence the encryption of the next block, improving security compared to ECB mode.
Step 4 — Build and Run
Compile the example using STM32CubeIDE.
Typical execution flow:
- Initialize cryptographic library
- Configure AES key and IV
- Perform encryption
- Perform decryption
- Compare output with original plaintext
When the demo runs successfully, the decrypted data matches the original plaintext buffer.
This confirms the correct operation of the AES implementation.
4. Hands-On: Deep dive explanation
This program demonstrates how to use the STM32 Cryptographic Library (cmox_crypto) to perform AES-128 encryption and decryption in CBC mode.
The main() function performs two demonstrations:
- Single-call encryption/decryption
- Multi-call (chunked) encryption/decryption
It also verifies that the results match
I’ll break it down step-by-step and explain the AES-CBC process and how the code implements it.
4.1. Overview of AES-CBC Used in This Code
The program uses AES-128 in CBC mode.
AES basics
- AES operates on 128-bit blocks (16 bytes).
- AES-128 uses a 16-byte key.
- The algorithm performs 10 rounds of transformations internally.
CBC (Cipher Block Chaining)
CBC ensures that each block depends on the previous block, increasing security.
Encryption formula:
Ci = AESenc(Pi ⊕ C(i-1))
Where:
Pi = plaintext block
Ci = ciphertext block
Ci−1 = previous ciphertext block
C0 = IV (Initialization Vector)
Decryption:
Pi = AESdec(Ci) ⊕ Ci−1
Example (first block)
Plaintext:
6bc1bee22e409f96e93d7e117393172a
IV:
000102030405060708090a0b0c0d0e0f
CBC first step:
InputBlock = Plaintext XOR IV
Then AES encrypts it.
This example uses official NIST vectors so the output can be validated.
4.2. Important Global Variables
AES Key
const uint8_t Key[] =
{
0x2b, 0x7e, 0x15, 0x16, ...
};
16 bytes → AES-128 key
Initialization Vector (IV)
const uint8_t IV[] =
{
0x00, 0x01, 0x02, ...
};
Also 16 bytes
Used only for the first block
Plaintext
const uint8_t Plaintext[];
64 bytes total.
AES block size = 16 bytes
64 / 16 = 4 blocks;
Expected Ciphertext
const uint8_t Expected_Ciphertext[];
This is the correct output defined by NIST.
The program checks that the library produces the same values.
Buffers
uint8_t Computed_Ciphertext[];
uint8_t Computed_Plaintext[];
These store the results generated by encryption/decryption.
4.3. Initialization in main()
HAL initialization
HAL_Init();
SystemClock_Config();
Initializes:
- STM32 hardware
- clock system
- SysTick timer
Initialize crypto library
cmox_initialize(&init_target);
This initializes the Cortex-M optimized crypto engine.
Without this call, encryption functions will fail.
LED initialization
BSP_LED_Init(LED4);
- success → LED ON
- error → LED blinking
4.4. Single-Call Encryption
The simplest usage.
retval = cmox_cipher_encrypt(
CMOX_AES_CBC_ENC_ALGO,
Plaintext,
sizeof(Plaintext),
Key,
sizeof(Key),
IV,
sizeof(IV),
Computed_Ciphertext,
&computed_size
);
Parameters explained
| Parameter | Meaning |
|---|---|
| CMOX_AES_CBC_ENC_ALGO | AES CBC encryption |
| Plaintext | input data |
| sizeof(Plaintext) | data length |
| Key | AES key |
| IV | initialization vector |
| Computed_Ciphertext | output buffer |
| computed_size | returned size |
Internally the library performs
block1 = AES(P1 ^ IV);
block2 = AES(P2 ^ block1);
block3 = AES(P3 ^ block2);
block4 = AES(P4 ^ block3);
Verification
memcmp(Expected_Ciphertext, Computed_Ciphertext);
Error_Handler();
4.5. Single-Call Decryption
retval = cmox_cipher_decrypt(
CMOX_AES_CBC_DEC_ALGO,
Expected_Ciphertext,
sizeof(Expected_Ciphertext),
Key,
sizeof(Key),
IV,
sizeof(IV),
Computed_Plaintext,
&computed_size
);
Internally:
P1 = AES_DEC(C1) ^ IV;
P2 = AES_DEC(C2) ^ C1;
P3 = AES_DEC(C3) ^ C2;
P4 = AES_DEC(C4) ^ C3;
memcmp(Plaintext, Computed_Plaintext);
4.6. Multiple-Call (Chunked) Encryption
This demonstrates streaming encryption.
- Data arrives in packets
- Data is too large for RAM
Create CBC context
cipher_ctx = cmox_cbc_construct(&Cbc_Ctx, CMOX_AES_CBC_ENC);
Initialize it
cmox_cipher_init(cipher_ctx);
Load key
cmox_cipher_setKey(cipher_ctx, Key, sizeof(Key));
Set IV
cmox_cipher_setIV(cipher_ctx, IV, sizeof(IV));
4.7. Chunk Encryption Loop
The program processes 48-byte chunks:
#define CHUNK_SIZE 48
Loop:
for (index = 0; index < (sizeof(Plaintext) - CHUNK_SIZE); index += CHUNK_SIZE)
Then:
cmox_cipher_append(...);
This function:
- encrypts the chunk
- updates CBC internal state
Important: the context remembers the last ciphertext block.
Chunk 1:
block1
block2
block3
Chunk 2:
block4
4.8. Last Partial Block
if (index < sizeof(Plaintext))
{
/* Encrypt remaining bytes */
}
4.9. Cleanup
cmox_cipher_cleanup(cipher_ctx);
Releases internal context resources.
4.10. Multi-Call Decryption
CMOX_AES_CBC_DEC
- construct context
- init
- set key
- set IV
- append ciphertext chunks
- verify plaintext
4.11. Finalization
cmox_finalize(NULL);
Shuts down cryptographic services.
Why the Example Uses NIST Test Vectors
These values come from NIST SP 800-38A.
Purpose:
- verify implementation correctness
- ensure AES works exactly as the standard
This is extremely common in
- secure firmware updates
- encrypted communication
- secure storage
- TLS stacks
- IoT authentication
Conclusion
Cryptography is an essential building block for secure embedded systems. Even small microcontrollers like the STM32U0 can implement robust cryptographic functionality using firmware libraries such as X-CUBE-CRYPTOLIB.
In this article we explored:
- The main cryptographic concepts used in embedded systems
- The STM32 cryptography ecosystem
- Typical use cases including encryption, hashing, and digital signatures
- A practical AES example running on STM32
This is only the starting point. In upcoming articles of this series we will continue exploring
- Secure boot and bootloader protection
- Firmware authenticity verification
- Secure firmware updates
- Device life-cycle management
These mechanisms combine with cryptography to create trustworthy embedded devices for IoT and industrial applications.


