Cryptography on STM32 Implementing Secure Embedded Systems with STM32U0

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:

  1. Encrypt a plaintext buffer using AES-CBC
  2. 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:

  1. Initialize cryptographic library
  2. Configure AES key and IV
  3. Perform encryption
  4. Perform decryption
  5. 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:

  1. Single-call encryption/decryption
  2. 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

  1. AES operates on 128-bit blocks (16 bytes).
  2. AES-128 uses a 16-byte key.
  3. 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

ParameterMeaning
CMOX_AES_CBC_ENC_ALGOAES CBC encryption
Plaintextinput data
sizeof(Plaintext)data length
KeyAES key
IVinitialization vector
Computed_Ciphertextoutput buffer
computed_sizereturned 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top