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.
| 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
0x04C11DB7give for the string1234567890xCBF43926and0x0376E6E7— 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.
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.
CECSmeans the block clock is below the permitted value: usually the 48 MHz branch is not configured.SECSis 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: clearSEIS, switchRNGENoff and on again, throw away the accumulated words. Code that waits only forDRDYwill 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.
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
- A CRC is a remainder of division, not a sum; there are six parameters, the polynomial is one of them; the hardware block of the F1/F4 computes the MPEG-2 variant in words.
- The G4/L4 block is programmable: polynomial, width, reflections — CRC-16/MODBUS can be computed on it too.
- The RNG produces a word in ~40 cycles of 48 MHz; the
SECSandCECSflags are read before the data, otherwise you get non-random "random" numbers. - Hash: SHA-256 is the working standard, MD5 and SHA-1 are for integrity only; on the hardware block watch the byte order.
- Cipher: AES in GCM mode is encryption with authentication; do not use ECB; in CTR and GCM the IV is never repeated with one key.
- The hardware block is an accelerator for the library, not a replacement for it.
- Keys are not kept in the sources.
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.