UNIT 15 · LESSON 2 OF 6

Image Integrity and Authenticity

Is it safe to run? Which checks answer which question, and what must stay secret for the answer to hold?

INTERACTIVEWhich check stops which change?
An image, its delivered check value, the recomputed value and the verdictimage307832303030313043303b424c206170delivered0xA5F0D770recomputed0xA5F0D770ACCEPTEDThe attacker recomputed the CRC over the patched image. A CRC protects against noise,not against a deliberate attacker.
An image, its delivered check value, the recomputed value and the verdictimage30783230303031304330delivered0xA5F0D770recomputed0xA5F0D770ACCEPTEDThe attacker recomputed the CRC over the patchedimage. A CRC protects against noise, not against adeliberate attacker.

Try this

Check
What happened to the image
CRC-32 in the image: accepted.

The bootloader recomputes a check value over the image and compares it with the one delivered with it. Random damage changes the image without fixing the check, so every method catches it (a CRC-32 misses only about one random corruption in 2³²). An attacker is different: they fix the check too. A CRC or a plain hash needs no secret, so anyone can recompute it. A MAC needs the secret key, but that key is in every device that verifies. A signature needs the private key, which never leaves the vendor; devices hold only the public key.

What you will be able to do
  • Explain why a CRC detects accidental corruption but cannot stop a deliberate change.
  • State the properties of a cryptographic hash and what a hash stored beside an image does and does not prove.
  • Distinguish a MAC from a digital signature by who can create and who can verify, and what each device must store.
  • State precisely the conditions under which a valid signature proves an image’s origin.
  • Describe how MCUboot hashes and signs an image and why encryption does not replace authentication.
Before you start
  • Checksums and CRCs (unit 10, lesson 6); the RP2040 boot2 CRC (unit 5, lesson 2).
  • The MCUboot image layout (lesson 1).
Steps in this lesson
  1. Checks against accidents: CRCs
  2. Fingerprints: cryptographic hashes
  3. Shared secrets: MACs
  4. Private and public keys: signatures
  5. Encryption is a different promise
  6. Worked example: why a CRC-32 cannot protect an image
  7. Common misconceptions

The puzzle

The bootloader finds a new image in the secondary slot, and the check value delivered with it matches the one it computes. Is it safe to run? A match tells you that the image is the one the check value was computed for. It does not tell you who computed it. Which checks answer which question, and what must stay secret for the answer to hold?

STEP 1

Checks against accidents: CRCs

A CRC (unit 10, lesson 6) is excellent against random damage: a CRC-32 detects every single-bit error, every error burst up to 32 bits long and all but about one in 2³² random corruptions. The RP2040 boot ROM uses one to reject a blank or half-written boot2 (unit 5, lesson 2). But a CRC is a public, linear function with no secret. Flipping a given bit changes the CRC by a pattern that depends only on the bit’s position and the message length:

CRC(m⊕e)=CRC(m)⊕CRC(e)⊕CRC(0)\mathrm{CRC}(m \oplus e) = \mathrm{CRC}(m) \oplus \mathrm{CRC}(e) \oplus \mathrm{CRC}(0)

for messages of equal length, so whoever changes an image can compute the new CRC, or adjust four bytes to keep the old one. A CRC protects against noise, not against a deliberate attacker.

INTERACTIVEFlip one bit: a CRC changes predictably, a hash does not
A 16-byte message with one bit flipped, its CRC-32 and its SHA-256 before and aftermessage667720312e322e33206275696c642037bit flipped667724312e322e33206275696c642037CRC-320x87083FD8 → 0x9C16196BCRC changeXOR 0x1B1E26B3SHA-25693ffe9d272889a6dbfe9e23908dc7564a0b01668342c30f2fa56d7810f083ade9f8342c79d806e11dbf53579c378b19b3d567274155f8958759ab634e85371b0SHA-256: 134 of 256 output bits changed (about half is expected for any change)Same bit flipped in the other message: CRC change XOR 0x1B1E26B3, identical. The CRCchange is known in advance, so a forged image can be given a matching CRC.
A 16-byte message with one bit flipped, its CRC-32 and its SHA-256 before and aftermessage667720312e322e33206275696c642037bit flipped667724312e322e33206275696c642037CRC-320x87083FD8 → 0x9C16196BCRC changeXOR 0x1B1E26B3SHA-25693ffe9d272889a6dbfe9e23908dc7564a0b01668342c30f2fa56d7810f083ade9f8342c79d806e11dbf53579c378b19b3d567274155f8958759ab634e85371b0SHA-256: 134 of 256 output bits changed (about halfis expected for any change)Same bit flipped in the other message: CRC changeXOR 0x1B1E26B3, identical. The CRC change is knownin advance, so a forged image can be given amatching CRC.
Message
CRC changes by XOR 0x1B1E26B3; SHA-256 changes 134 of 256 bits.

A CRC is linear: flipping a given bit changes the CRC by a fixed pattern that depends only on the bit’s position and the message length, not on the data. That is why an attacker can patch an image and repair its CRC with a little arithmetic. A cryptographic hash such as SHA-256 is designed so that any change alters about half of its 256 output bits unpredictably, and so that nobody knows a practical way to find a second input with the same hash. The CRC here is the RP2040 boot ROM’s CRC-32/MPEG-2.

STEP 2

Fingerprints: cryptographic hashes

A cryptographic hash such as SHA-256 maps any input to 256 bits so that nobody knows a practical way to find two inputs with the same hash (a collision), or a second input matching a given hash. Any change, even one bit, alters about half the output bits unpredictably. A hash is therefore a trustworthy fingerprint, but only of the image it was computed from, and only if you got the fingerprint from somewhere trustworthy. MCUboot requires a SHA-256 record in every image and compares it with its own computation; that catches corruption, but whoever replaces the image replaces the record too. The hash earns its place because the signature covers it.

STEP 3

Shared secrets: MACs

A message authentication code mixes a secret key into the hash. HMAC, the usual construction, pads the key K to the hash’s 64-byte block and hashes twice:

HMAC(K,m)=H((K⊕opad) ∥ H((K⊕ipad) ∥ m))\mathrm{HMAC}(K, m) = H\big((K \oplus \mathrm{opad}) \,\|\, H((K \oplus \mathrm{ipad}) \,\|\, m)\big)

with ipad the byte 0x36 and opad 0x5C repeated. Without K nobody can produce a matching tag. The catch is symmetry: verifying needs the same K as creating. Every device that checks a MAC-protected image holds the key that makes valid images, so the scheme is only as strong as the key’s protection in the least protected device (lesson 5). Compare tags with a constant-time function, such as psa_mac_verify() in the PSA Crypto API, not memcmp(), whose running time can reveal how many leading bytes matched.

STEP 4

Private and public keys: signatures

A digital signature splits the key. The vendor signs with a private key that never leaves its signing system; devices verify with the matching public key, which is not secret. MCUboot hashes the header, payload and protected records, signs that hash (ECDSA, Ed25519 or RSA-PSS, chosen when the bootloader is built), and verifies

Verify(pk, SHA256(header ∥ payload ∥ protected records), σ)\mathrm{Verify}\big(pk,\ \mathrm{SHA256}(\text{header} \,\|\, \text{payload} \,\|\, \text{protected records}),\ \sigma\big)

with a public key built into the bootloader (or, as lesson 4 shows, one whose hash is stored in hardware).

↑ This step uses the figure at the top of the page.

Be precise about what a valid signature proves: that the image was signed with the private key matching the verifier’s public key. That proves the image’s origin only as long as the private key has stayed secret, and the verifying code and its public key cannot be changed by the attacker. If either can be rewritten, the check can be removed or the key replaced (lesson 4). And a valid signature says nothing about whether the image is bug-free or current: an old, vulnerable release is still validly signed (lesson 6).

CheckNeeded to create a valid oneStops random damageStops someone who can write the image
CRCnothingyesno
SHA-256 stored beside the imagenothingyesno
HMACthe shared key, present in every verifieryesuntil any verifier’s key leaks
Signaturethe private key, held only by the signeryeswhile the private key is secret and the verifier and public key are immutable

STEP 5

Encryption is a different promise

Encryption provides confidentiality: someone who copies the image in transit, or reads it from an external flash chip, cannot read its contents. It does not provide authenticity. MCUboot’s encrypted images use AES in counter mode, where flipping a bit of the ciphertext flips the same bit of the decrypted image, so decryption alone would not notice a change; MCUboot still hashes and signs the unencrypted image. Its documentation is explicit that encryption does not protect an image in internal flash against someone who can read it through the debug port.

STEP 6

Worked example: why a CRC-32 cannot protect an image

Take the 16-byte message “fw 1.2.3 build 7” and flip bit 21 (counting from the most significant bit of the first byte, as the figure does). Its CRC-32 (the RP2040 ROM’s variant) changes from 0x87083FD8 to 0x9C16196B, a difference of

0x87083FD8⊕0x9C16196B=0x1B1E26B30x87083FD8 \oplus 0x9C16196B = 0x1B1E26B3

Flip the same bit of “fw 9.9.9 build 0” and the CRC changes by the same 0x1B1E26B3. Nothing about the data was needed: the change is known in advance. Against random corruption the CRC is still good; a random 32-bit check value matches by chance with probability

2−32≈14.3×1092^{-32} \approx \frac{1}{4.3 \times 10^{9}}

Against an attacker it is worth nothing. SHA-256 of the two versions of the first message differ in 134 of 256 bits, with no pattern to exploit; but even SHA-256 only helps if the attacker cannot replace the reference hash, which the signature over it provides, under the assumptions above.

MYTHS AND FACTS

Common misconceptions

The CRC matches, so the image is genuine

A CRC only detects accidents; anyone can compute one.

A hash in the image header proves it came from us

Whoever changes the image recomputes the hash; only a secret-keyed check proves origin.

A MAC is as good as a signature

Every verifier holds the MAC key; a signature’s verifiers hold nothing that signs.

Encrypted firmware can’t be tampered with

Encryption hides content; only a MAC or signature detects changes.

A valid signature means the image is safe to run

It proves who signed it, under the stated assumptions, not that it is the latest or bug-free.

Check yourself

Answer in your head, then open the card.

An image carries its CRC-32 and its SHA-256 in the header. An attacker changes a constant in the code. What else must they change for the bootloader to accept it?

Both the CRC and the SHA-256, which they can compute themselves: neither needs a secret. Only a MAC or a signature would stop them.

A product uses HMAC-SHA-256 with one key programmed into every device. Why does opening a single device threaten the whole product line?

Verifying needs the same key as creating. The key read out of one device lets the attacker compute valid tags for any image, which every other device accepts.

Under what conditions does a valid ECDSA signature on an image prove it came from the vendor?

The vendor’s private key must be secret (never leaked, never used by anyone else), and the attacker must not be able to change the verifying code or the public key it uses. Even then it proves origin, not that the image is recent or correct.

Why should firmware compare a MAC with a constant-time function instead of memcmp()?

memcmp() may return at the first differing byte, so its timing can reveal how much of a guessed tag was right, letting an attacker find a valid tag byte by byte.

Sources (6)
  1. MCUboot v2.1.0, docs/design.md (“Integrity check”, “Security”, “Protected TLVs”) — the bootloader checks the magic, requires a SHA256 TLV and compares it with the computed hash; a signature TLV must come with a KEYHASH TLV; the hash covers header, payload and protected TLVs, and the signature is over the hash; the bootloader “verifies that an image was signed with a private key that corresponds to the embedded KEYHASH TLV”
  2. MCUboot v2.1.0, boot/bootutil/src/image_validate.c (bootutil_img_validate) — computes bootutil_img_hash, compares it with the SHA-256 record using boot_fih_memequal, looks up the key by its hash, then calls bootutil_verify_sig(hash, …); unprotected TLVs other than the allowed ones cause a failure
  3. MCUboot, docs/signed_images.md and docs/encrypted_images.md — “This signs the image by computing hash over the image, and then signing that hash … The public key of this keypair must be included in the bootloader”; encrypted images use AES-CTR, “Hashing and signing also remain functionally the same way as before, applied over the un-encrypted data”, and encryption “does not protect against the possibility of attaching a JTAG and reading the internal flash memory”
  4. Mbed TLS v3.6.0, include/psa/crypto.h (psa_hash_compare, psa_mac_compute, psa_mac_verify, psa_verify_hash) — “Beware that comparing integrity or authenticity data such as MAC values with a function such as memcmp is risky because the time taken by the comparison may leak information about the MAC value”; verify functions compare “in constant time”
  5. CPython v3.12.0, Lib/hmac.py — “Implements the HMAC algorithm as described by RFC 2104”: the key is hashed if longer than the block size, padded with zeros to 64 bytes, and XORed with 0x36 for the inner and 0x5C for the outer hash
  6. Linux v6.10, Documentation/admin-guide/module-signing.rst — signing and verifying with a key pair outside embedded systems: the kernel holds public keys and “will only load validly signed modules for which it has a public key”; “Since the private key is used to sign modules, viruses and malware could use the private key to sign modules”