Embedded HandbookApps
All sections › III. Peripherals
STM32F103, STM32F407, STM32F437, STM32L4A6 · level advanced · 15 min read
Open in the interactive handbook

43. Hardware cryptography: CRC, RNG, HASH, CRYP

Four blocks that answer four different questions: has the data been corrupted (CRC), where to get a number nobody can guess (RNG), is this the same data as before (HASH), and how to make it readable only by those entitled to it (CRYP). The first two exist on almost every STM32, the other two on some families, and where they are absent, a library does the same work.

The section relies on five chips: the F103 and F407 — the familiar ones, the F437 — like the F407 with HASH and CRYP blocks, the L4A6 — with a compact AES, the G474 — with a CRC whose polynomial can be set.

Terms used in the documentation

Term Meaning
cyclic redundancy check (CRC) the remainder of a polynomial division, a "checksum"
generator polynomial the divisor of the CRC
initial value / final XOR the starting value / the XOR applied at the end
reflected input / output input / output with reversed bit order
true random number generator (TRNG) a generator based on physical noise
seed error / clock error a noise source failure / a clock failure
hash, digest a fixed-length fingerprint of data
collision two messages with one hash
pre-image resistance impossibility of recovering the input from the hash
HMAC a keyed hash that proves authenticity
block cipher a cipher working on fixed-size blocks
ECB / CBC / CTR / GCM modes of operation of a block cipher
initialization vector (IV), nonce a value used once per message
authentication tag the value that proves authenticity
key derivation preparing the key for use

43.1 CRC: not a sum but the remainder of a division

The word "checksum" has stuck, but a CRC is not a sum. It is the remainder of dividing the message by a given number, and the division is done on bits by rules where addition is exclusive OR. Hence the main property: every bit of the message affects the result, and a single flipped bit changes the CRC.

The divisor is written as a polynomial: x⁸ + x² + x + 1. The powers are the positions of the ones in the number; the highest power is implied and omitted, so a polynomial of degree eight is written with eight bits: 0x07. For degree sixteen, 0x8005 means x¹⁶ + x¹⁵ + x² + 1.

CRC-8 with polynomial 0x07 for the byte 0x31 step by step: shift and XOR when a one falls out

Name Polynomial Where it is used
CRC-8 0x07 one-wire sensors, short frames
CRC-16/MODBUS 0x8005, reflected, initial 0xFFFF Modbus RTU (section 16)
CRC-16/XMODEM 0x1021, initial 0x0000 XMODEM, SD cards (section 41а)
CRC-16/CCITT-FALSE 0x1021, initial 0xFFFF many protocols
CRC-32 0x04C11DB7, reflected, initial and final 0xFFFFFFFF Ethernet, ZIP, PNG
CRC-32/MPEG-2 0x04C11DB7, without reflections and final XOR the STM32 hardware block

⚠️ The polynomial is only one of six parameters. Width, polynomial, initial value, final XOR, input reflection and output reflection. Two CRCs with the same polynomial 0x04C11DB7 give for the string 123456789 0xCBF43926 and 0x0376E6E7 — because the other five parameters differ. "The CRC did not match the computer" almost always means exactly this, not an error in the data.

The "CRC" tab in the app's calculators computes all five variants from the table for the same bytes — you see at once which of them matches the other side, and what the hardware block gives after padding to whole words.

Checking on reception: the message has its CRC appended and the CRC is computed again. For variants without a final XOR the result is zero — if the CRC is appended in the right byte order: for reflected ones (MODBUS) least significant byte first, for direct ones (MPEG-2) most significant first. For variants with a final XOR the result is not zero but a constant: for CRC-32 it is 0x2144DF1C. It is simpler and clearer to compute the CRC over the data without the appended value and compare it with the received one.

The hardware block

On the F1 and F4 the block is fixed: polynomial 0x04C11DB7, initial value 0xFFFFFFFF, no reflections, data in 32-bit words.

RCC->AHBENR |= RCC_AHBENR_CRCEN;
CRC->CR = CRC_CR_RESET;            /* к начальному значению */
for (i = 0; i < words; i++) {
    CRC->DR = buf[i];              /* по слову */
}
result = CRC->DR;

Two consequences. First: the result matches CRC-32/MPEG-2, not what an archiver computes — compare with the computer using the same variant. Second: the block takes a word from the top bit, while a word in Cortex‑M memory lies least significant byte first (section 3а). The bytes '1', '2', '3', '4' reach the block as 0x34333231, and it processes '4' first: for the string 12345678 the result is 0xFEFC54F9 — the CRC-32/MPEG-2 of the string 43218765. To get a CRC over the byte stream, as on a computer (0x49E3C2FB), each word is reversed: CRC->DR = __REV(buf[i]);. The buffer length must then be a multiple of four bytes.

Bytes 1, 2, 3, 4 in memory and the same word in the CRC register: without reversal the block processes byte 4 first and gives the CRC-32/MPEG-2 of the string 43218765, with the REV instruction byte 1 first and the CRC of the string 12345678, as on a computer

On the G4 and L4 the block is programmable: POL sets the polynomial, INIT the initial value, POLYSIZE the width 7/8/16/32, REV_IN and REV_OUT the reflections, and data can also be fed in bytes: how many bytes go into the block is decided by the width of the write to DR — a 32-bit write sends four, an 8-bit write one. This way CRC-16/MODBUS is also computed on it:

CRC->POL  = 0x8005u;
CRC->INIT = 0xFFFFu;
CRC->CR   = (1u << CRC_CR_POLYSIZE_Pos)     /* 16 бит */
          | (1u << CRC_CR_REV_IN_Pos)       /* отражение по байтам */
          | CRC_CR_REV_OUT
          | CRC_CR_RESET;
for (i = 0; i < n; i++)
    *(volatile uint8_t *)&CRC->DR = data[i];   /* запись байта, а не слова */
crc16 = (uint16_t)CRC->DR;                     /* результат — младшие 16 бит */

An ordinary write CRC->DR = data[i]; is 32-bit: together with the byte, three zero bytes go into the block, and for the string 123456789 instead of 0x4B37 you get 0xB1C1.

Together with DMA the block computes the CRC of a buffer almost without the core — this is the standard way of checking the firmware at startup (section 48).


43.2 RNG: where randomness comes from

A pseudo-random sequence from a formula (rand, LFSR) is fine for a screen saver and useless for a key: anyone who learns the initial value will repeat it. Cryptography needs a source that cannot be reproduced — physical noise.

On the STM32 this is several ring oscillators whose analog noise is combined and sampled by a shift register. The block is clocked from the same 48 MHz branch as USB (section 20), and produces one 32-bit word in about 40 cycles of that clock — about a microsecond, up to 1.2 million words per second.

RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN;
RNG->CR |= RNG_CR_RNGEN;

if (RNG->SR & (RNG_SR_CECS | RNG_SR_SECS)) {
    /* ошибка: число использовать нельзя, перезапустить блок */
}
while (!(RNG->SR & RNG_SR_DRDY)) { }
value = RNG->DR;

⚠️ The two error flags are read before the data, not after. CECS means the block clock is below the permitted value: usually the 48 MHz branch is not configured. SECS is a failure of the noise source: the block saw more than 64 identical bits in a row or more than 32 alternations of 0 and 1 in a row, and everything it produced until restarted must not be considered random. Recovery: clear SEIS, switch RNGEN off and on again, throw away the accumulated words. Code that waits only for DRDY will get numbers that look random — and this is the worst kind of failure, because it goes unnoticed.

The block itself does not catch two identical words in a row. If the product needs compliance with certification requirements, the program does it: the first word after power-on is not used, and each subsequent one is compared with the previous one and a match is treated as a failure. The second rule is do not throw away "ugly" values: discarding zeros or repeated bytes makes the distribution non-random.

Where to get randomness where there is no block (F103): the noise in the low bits of the ADC on a floating input, phase jitter between two independent oscillators (HSI against LSI), the time until a button press. Each source on its own is weak; they are mixed with a hash — and that is the next block.

When cryptographic strength is not needed — spreading the pauses of a retry (section 186), "out of step" blinking, test data — a formula will do. But not just any formula.

The linear congruential generator x = x × 1664525 + 1013904223 modulo 2³² looks random only in its high bits. Its low bit strictly alternates — in the experiment 010101010101 — the second bit repeats every 4 steps, the third every 8, the fourth every 16. Taking x % 2 for "heads or tails" means getting an alternation; take the high bits, x >> 16.

The low bits of a linear congruential generator repeat every 2, 4, 8 and 16 steps

xorshift32 is three shifts and three exclusive ORs:

x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;

It goes through all non-zero 32-bit values once each: in the experiment, from one until returning to it took 4 294 967 295 steps, that is 2³² − 1. Zero is a trap: the generator never leaves zero, so the initial value must not be zero. On Cortex-M4 a step takes three EOR instructions — the shift is built into the instruction itself.


43.3 HASH: a fingerprint of data

A hash function turns a message of any length into a string of fixed length so that there is no way back, and finding two messages with the same hash is practically impossible. Changing one bit of the input changes about half the bits of the output — this is called the avalanche effect.

Algorithm Result length Status
MD5 16 bytes broken: collisions are found in seconds; integrity checks only
SHA-1 20 bytes broken for signatures since 2017
SHA-256 32 bytes the working standard
SHA-512 64 bytes faster on 64-bit machines, slower on Cortex‑M

Three things a hash is needed for in a device:

Firmware integrity. The bootloader computes the SHA-256 of the image and compares it with the stored value — this is more precise than a CRC: a CRC catches random corruption, while finding another image with the same SHA-256 is practically impossible. But a hash by itself does not protect against substitution: whoever substituted the image will write its hash next to it — just as with a CRC (section 48.4). Only a reference you can trust protects: a signature (section 48а), an HMAC with a secret key or a hash in a write-protected area.

Storing a secret without the secret. An access password is stored not as itself but as its hash: it can be checked but not recovered. With a caveat: a short password will be found with a dictionary of hashes, so a random "salt" is added to it and it is hashed many times.

Proving authenticity. HMAC is a hash with a key mixed in: a message with a valid HMAC could only have been composed by someone who knows the key. This is protection against forged commands to the device, cheaper than encryption.

HMAC proves who composed the message, but not when: an intercepted command can be sent again, and its tag is valid. So a command counter goes into the signed part — the device accepts only a number greater than the last one — or a nonce issued by the device itself. And the tag is compared in constant time, all bytes to the end: memcmp is allowed to return at the first mismatch, and from the response time the tag can be guessed byte by byte:

uint8_t diff = 0;
for (i = 0; i < 32; i++)
    diff |= tag[i] ^ expect[i];    /* все 32 байта, без выхода по дороге */
ok = (diff == 0);

The hardware block on the F437/L4A6 computes SHA-1, SHA-256 and MD5, supports HMAC and accepts data through DMA. Data is fed in words; the DATATYPE register says how to swap the bytes: the string abc lies in memory as 61 62 63, as a word it is 0x00636261, and without the right DATATYPE the hash is computed over a different byte sequence. This is the second most common source of the question "why does the hash not match the computer". The third is length: the last word is not completely filled, and the number of valid bits in it is written to the NBLW field of the HASH_STR register (24 for abc). Without this an extra zero byte is added to the data.


43.4 CRYP: encryption

A block cipher takes a block of fixed length and a key and produces a block of the same length. AES is the standard: a 16-byte block, a key of 16, 24 or 32 bytes (128/192/256 bits). DES and triple DES remain only for compatibility.

The key question is not "which cipher" but "in which mode":

Mode What it does Use it?
ECB each block separately no: identical blocks give identical ciphertext, the picture shows through
CBC a block is chained to the previous one through the IV for files; requires padding to 16 bytes
CTR the cipher is turned into a stream: any length for data streams
GCM CTR plus an authentication tag yes, by default

GCM gives what the others lack: a substituted byte of ciphertext does not decrypt "into garbage", it fails the tag check of 16 bytes. A device that accepts commands must know that the command has not been forged — encryption without authentication does not give that.

⚠️ In CTR and GCM the nonce is never repeated. Two messages with the same key and the same IV give away the XOR of their plaintexts, and in GCM they also allow the tag to be forged. The IV is taken either from a counter stored in Flash that survives a reboot, or as 96 bits from the RNG. A counter in RAM that starts from zero after reset is a ready-made vulnerability. The key is changed before the counter can overflow.

A 60-byte message in CBC takes 64 (four blocks with padding), in CTR and GCM exactly 60, plus the tag in GCM.

The hardware block speeds up the operation — on the F437 this is CRYP with DES/TDES/AES and an eight-word FIFO, on the L4A6 a compact AES with only AES-128/256 and the ECB/CBC/CTR/GCM modes, both with DMA input. A subtlety of the L4: decryption in ECB/CBC requires a prepared key — a separate MODE before decryption; in CTR and GCM this is not needed, because there the cipher works only in the forward direction — one more argument for them.

⚠️ The block is not a library. The registers perform one block transformation. Chaining modes, padding, the GCM tag, key exchange, signatures — are code on top. Writing it yourself is a sure way to get a vulnerability: take a ready library, and it will use the block itself if there is one.

The manufacturer's library covers all families, including those with no hardware blocks at all: AES in all modes, SHA-1…SHA-512 with HMAC, a software random number generator, RSA, elliptic curves and ECDSA signatures. It comes prebuilt for Keil, IAR and GCC; connecting it is the path to the headers and the library name in the linker (section 4).

⚠️ A secret in the sources is not a secret. An encryption key in the array const uint8_t key[16] can be read out of the firmware in a minute. Keys are kept in a protected area with read protection enabled (section 11) or obtained through an exchange at the first connection; in the sources — only in test examples, and knowing this.


43.5 What protects against what

Task Means Block
a frame over UART/CAN was not corrupted in transit CRC-16 software or hardware CRC
the firmware in Flash memory is intact CRC-32 at startup CRC + DMA
the firmware has not been substituted SHA-256 + a signature HASH or a library
a command to the device comes from our own server HMAC-SHA-256 HASH or a library
an outsider cannot read the data AES-GCM CRYP / AES or a library
a session key, a nonce RNG RNG
an access password is stored safely salt + repeated SHA-256 HASH or a library

The rule of choice: CRC against interference, a hash against unnoticed corruption, a signature or HMAC against substitution and forgery, a cipher against reading. Each next one does not replace the previous one but solves a different task.


43.6 Pitfalls

The CRC does not match the computer. Different parameters with the same polynomial; the hardware block of the F1/F4 uses the MPEG-2 variant, 32-bit words, no reflections, and takes the bytes of a word from the top: without __REV the CRC comes out over swapped bytes (43.1).

The hash does not match the computer. DATATYPE is not switched to bytes; the block hashes the swapped bytes of the words.

The RNG gives identical numbers. The block itself does not catch repeats: SECS reports a noise source failure, not a repeated word. Check SECS and CECS before reading and wait for DRDY; on SECS clear SEIS and restart the block. Where a continuous test is needed, the program compares a word with the previous one.

The RNG is never ready. CECS: no 48 MHz clock — the PLL branch is not configured, as for USB.

Decryption gives garbage. The wrong key, the wrong IV, the wrong mode; in ECB/CBC on the L4 the key preparation before decryption was not done.

GCM rejects with the correct key. At least one byte of the ciphertext, header or tag was substituted or corrupted — this is not an error but the mode doing its job.

A foreign command passed the GCM check. The IV repeated with the same key — for example, a counter in RAM started from zero after reset: from two messages with one IV the tag is forged without the key.

ECB "works", but the ciphertext repeats. Identical blocks give identical ciphertext; change the mode to GCM.

The key is visible in the firmware. An array in the sources. Read protection and storage outside the code.

The library does not link. The variant taken is for the wrong core or without the hardware block when the block is present; the archive has different builds.


43.7 Summary

Next: section 43а "Where TLS gets its randomness: the hardware RNG, your own entropy source and quality checks" — how the firmware seeds the mbedTLS generator: an F407 RNG driver with error handling, your own source on the F103 and on-line source tests.

Open in the interactive handbook