The puzzle
Every guarantee in the last three lessons ended with “as long as the key is secret”. So where do the keys actually live, who can read them, and what does an attacker gain by buying one of your devices, opening it and extracting everything inside?
STEP 1
Where each key lives
Follow each key to the place it is stored:
- The signing private key lives with the vendor, ideally in a hardware security module or at least a machine that does nothing else. It never goes into a device, a repository or a developer’s laptop. MCUboot ships a development key “widely distributed”, and its documentation says it “should never be used for production”; the same goes for TF-M’s default keys.
- The verification public key, or its hash, lives in the device. It is not secret, but it must be unchangeable (lesson 4).
- Symmetric keys (a MAC key, an image-decryption key, a key shared with a server) live in the device and must be secret. MCUboot’s encryption documentation puts the burden plainly: the manufacturer must ensure such a key is “not possible to extract”.
Design as if a determined attacker can read anything stored in a device they own. Then ask how many devices that one extraction exposes.
STEP 2
One key per device
If a device must hold a symmetric secret, make it unique. A common way derives each device’s key from a master key and the device’s ID:
The device stores only ; the server stores only and recomputes when it needs it. Extracting compromises one device. The master key is now the crown jewel, kept where the signing key is kept. Signatures are better still where they fit: the device holds nothing that signs, so opening it yields nothing to forge with.
STEP 3
Identifier or identity?
A serial number is an identifier: it says which device claims to be talking. Anyone who reads it can repeat it. The RP2040 has no on-chip ID at all; the pico-sdk’s pico_get_unique_board_id() returns the 64-bit ID of the external flash chip, useful to tell boards apart, but readable by any code and not a secret. The RP2350’s OTP separates a public 64-bit CHIPID from a private 128-bit per-device random number, RANDID.
An identity is proven, not stated: the device shows it holds a secret nobody else has, usually by signing a fresh challenge from the server with a per-device private key whose public half the server registered at manufacture. A device with a readable ID and no secret can be impersonated by anyone who has read the ID.
STEP 4
Closing the debug port
The debug port that halted your code in unit 6 can, left open, read memory, keys included, and rewrite flash. Production devices close it, and chips offer different locks:
- The STM32F4’s readout protection (RDP) is an option-byte setting. Level 1, “read protection of the memory”, keeps flash contents from the debugger and blocks flash programming and erasing while debug is connected, and can be set back to level 0, but that regression mass-erases the flash, so it reopens the device for new code without revealing the old contents. Level 2, “full chip protection”, is permanent: the HAL warns it is “no more possible to go back to level 1 or 0”.
- The RP2350 has OTP flags: SECURE_DEBUG_DISABLE (“Disable Secure debug access”) and DEBUG_DISABLE (“Disable all debug access”). OTP bits cannot be cleared.
- Armv8-M cores such as the Cortex-M33 separate invasive debug (roughly, halting and changing the core) from non-invasive debug (observing it, such as tracing), each for Secure and Non-secure state; the chip decides which are allowed through the debug authentication signals CMSIS exposes as DAUTHCTRL and DAUTHSTATUS.
A debug port that can halt the core and read memory also reads keys and rewrites firmware, so production devices close it. Chips differ in how: the STM32F4’s readout protection (RDP) has two levels, only the first reversible; the RP2350 has one-time-programmable OTP flags that disable Secure debug or all debug. A permanent lock also shuts out your own failure analysis of returned units, so plan how a locked device is diagnosed.
Lock at the end of production, after programming and testing, as a scripted step. A permanent lock also locks you out: failure analysis of a returned unit must then work from logs, crash records and a recovery path designed in advance (lesson 6), or from whatever authenticated unlock the chip supports.
STEP 5
Keeping secrets away from code that does not need them
Inside the device, give each secret to as little code as possible:
- keep keys in memory that ordinary code cannot read: the RP2350 can lock OTP pages against Non-secure access, permanently, and TrustZone keeps Secure memory out of reach of Non-secure code;
- use keys through an API that never hands them out: a PSA Crypto key created without export permission is used by its ID (for example to verify a MAC) and its bytes never reach application code; MCUboot’s
MCUBOOT_BUILTIN_KEYapplies the same pattern to its (public) verification keys; - compare MACs, hashes and passwords in constant time (lesson 2);
- never log, print or put keys in crash dumps.
STEP 6
Worked example: one extracted key, three designs
A fleet of 10 000 devices; an attacker buys one and extracts its secrets.
- One shared HMAC key: the key verifies, and therefore forges, images for all 10 000.
- Per-device keys: for chip ID e6613852d34a2b19 and the demonstration master key in the figure, = HMAC-SHA-256(master, ID) begins b5acb064 2da62ea0. It forges images for exactly one device, the one already in the attacker’s hands.
- Signatures: the device holds a public key; nothing extracted can sign. 0 devices exposed.
Now suppose instead the vendor’s build server is breached. The shared key, the master key and the signing key each expose all 10 000 devices. Signatures recover best, provided the second signing key was held separately (offline or in a different HSM): devices provisioned with its hash (lesson 4) can revoke the stolen key and accept releases signed with the new one, without touching the hardware.
MYTHS AND FACTS
Common misconceptions
Nobody can read our flash
Assume anyone who owns a device can, eventually; design so that what they read does not break other devices.
The serial number authenticates the device
A readable number is an identifier; identity needs a secret the device can prove it holds.
Readout protection level 1 is enough
It protects reading and can be set back to level 0 (erasing the flash), so it is not a trust anchor: new code can then be loaded; the permanent lock is level 2 (on the STM32F4), with the cost that it can never be undone, even by you.
The development key is fine until we ship
Devices provisioned with it accept anything signed by the widely distributed private key; production keys go in before the first production unit.
Check yourself
Answer in your head, then open the card.
A product verifies firmware with HMAC and one key in every unit. What is the smallest change that stops one extracted key from exposing the whole fleet, and what new asset must be protected?
Per-device keys, derived as HMAC(master, device ID) and programmed at manufacture. The server must now guard the master key, which can derive every device key.
Why is the RP2040’s pico_get_unique_board_id() not suitable as a secret for authenticating a device to a server?
It is the flash chip’s ID, readable by any code (and by anyone with the chip), so it identifies the board but proves nothing; it also changes if the flash chip is replaced.
An STM32F4 product ships with RDP level 1. What does that protect, and what does it not guarantee?
The debugger cannot read flash contents, and flash cannot be programmed or erased while debug is connected. It is reversible (it can be set back to level 0), so it is not a permanent lock.
Name two ways firmware can keep a key usable but hard to leak inside the device.
Any two of: keep it in memory locked against Non-secure or application access (OTP page locks, TrustZone Secure memory); use it through a crypto API by key ID so the key is never copied out; never log it or put it in crash dumps; compare secrets in constant time.
Sources (7)
- MCUboot v2.1.0, docs/imgtool.md, and MCUboot docs/signed_images.md — the key file “should be protected, and not widely distributed”; the distributed development key “should never be used for production”; signed_images.md: separate “signing custody (a production private key, held only by a release team) from verification custody”, so that “the private key is never present on developer workstations”
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_flash_ex.h and Src/stm32f4xx_hal_flash_ex.c — OB_RDP_LEVEL_0 “No protection” (0xAA), OB_RDP_LEVEL_1 “Read protection of the memory” (0x55), OB_RDP_LEVEL_2 “Full chip protection” (0xCC): “WARNING: When enabling
OB_RDPlevel 2 it’s no more possible to go back to level 1 or 0”; at level 1 “it is not possible to program or erase the flash sector i if CortexM4 debug features are connected or boot code is executed in RAM”; read-out protection can be modified “from level1 to level0” - Raspberry Pi Ltd, pico-sdk 2.0.0, src/rp2350/hardware_regs/include/hardware/regs/otp_data.h — CRIT1.DEBUG_DISABLE “Disable all debug access”, CRIT1.SECURE_DEBUG_DISABLE “Disable Secure debug access”; CHIPID0–3 “public device ID” (64 bits), RANDID0–7 “private per-device random number” (128 bits); page lock states are “Thermometer-coded, so lock state can be advanced permanently from any state to any less-permissive state by programming OTP”, separately for Secure and Non-secure access
- Arm, CMSIS 6 v6.1.0, CMSIS/Core/Include/core_cm33.h (DCB DAUTHCTRL, DIB DAUTHSTATUS) — Armv8-M debug authentication distinguishes invasive and non-invasive debug, each for Secure and Non-secure state: DAUTHCTRL SPIDENSEL/INTSPIDEN and SPNIDENSEL/INTSPNIDEN; DAUTHSTATUS SID, SNID, NSID, NSNID fields
- Raspberry Pi Ltd, pico-sdk 1.5.1, pico_unique_id/include/pico/unique_id.h — the RP2040 has no on-chip unique ID; pico_get_unique_board_id() returns the external NOR flash’s “64-bit unique ID”, “suitable for use as a unique identifier for an RP2040-based board”
- MCUboot, docs/encrypted_images.md (“Threat model”) — “Since decrypting requires a private key (or secret if using symmetric crypto) to reside inside the device, it is the responsibility of the device manufacturer to guarantee that this key is already in the device and not possible to extract”
- MCUboot v2.1.0, docs/design.md (“Using hardware keys for verification”) — MCUBOOT_BUILTIN_KEY: “neither the code nor the image metadata needs to contain any public key data. During image validation only a key ID is passed to the verifier function. The key handling is entirely the responsibility of the crypto library”