Embedded Security: Secure Boot, Crypto & Hardening

Connected devices are attack targets. Learn threat modelling for embedded products, the chain of trust and secure boot, signed and encrypted firmware updates, cryptographic building blocks (hashes, MACs, AES, ECDSA, key exchange), key storage in secure elements and TrustZone, MPU-based isolation, debug-port locking, side-channel and fault-injection attacks, and regulatory requirements such as the EU Cyber Resilience Act and IEC 62443.

An attacker who controls your firmware controls the product: they can steal data, brick devices, turn them into botnets (Mirai infected ~600 000 IoT devices), or — in cars and medical devices — endanger lives. Security must be designed in from the first schematic, not bolted on at the end.

The core ideas are few: only run code you have verified, protect secrets in hardware, minimise the attack surface, and be able to fix vulnerabilities in the field with authenticated updates.

An analogy

Secure boot is a chain of guards: the first guard (immutable ROM) checks the ID of the second (bootloader), which checks the third (application). If any ID is forged, the door stays shut. Keys are the guards' seals — kept in a safe (secure element), never on a sticky note (plain Flash).

Threat modelling first

Ask systematically: what assets, which attackers, which entry points?

Chain of trust and secure updates

Secure boot starts from an immutable root of trust (ROM or locked bootloader) that verifies the next stage's signature before running it. Updates (OTA) must be signed (authenticity), optionally encrypted (confidentiality), version-checked (anti-rollback), and installed atomically with A/B slots or a fallback so a power cut can't brick the device. MCUboot is a widely used open-source secure bootloader.

Protecting keys and code

Never hard-code shared secrets in firmware — one extracted device then compromises all. Use per-device keys provisioned in the factory, stored in a secure element (ATECC608, SE050), a TrustZone secure world (Cortex-M33 with TF-M), or OTP/key-slots in the MCU's crypto engine. Lock the debug port (readout protection), use the MPU to make stacks non-executable and isolate tasks, and disable unused interfaces.

Physical attacks and compliance

Side-channel attacks read keys from power consumption or electromagnetic emission (DPA); fault injection (voltage/clock glitches, lasers) skips instructions such as the signature check. Countermeasures: hardware crypto with masking, constant-time code, redundant checks, glitch detectors, and tamper sensors. Regulation now requires security: the EU Cyber Resilience Act, ETSI EN 303 645 for consumer IoT, IEC 62443 for industrial, UN R155/ISO 21434 for vehicles — including vulnerability handling and security updates for the product's lifetime.

Formulas

Worked example: Brute-forcing AES-128

Given: 10¹² guesses per second

Find: Time to search the whole keyspace.

Answer: ≈ 10¹⁹ years — attackers go after keys and implementation flaws instead

Worked example: Weak legacy crypto

Given: 56-bit DES key, 10¹² guesses/s

Find: Worst-case time.

Answer: ≈ 20 hours — never use DES

Worked example: Default passwords

Given: Random 8-char lowercase+digit password (36⁸); Offline attack at 10⁹/s

Find: Time to exhaust.

Answer: ≈ 47 min — use unique per-device credentials, rate limiting and certificates

Verifying a signed application image before jumping to it

#include "mbedtls/sha256.h"
#include "mbedtls/pk.h"

typedef struct {
    uint32_t magic, version, size;
    uint8_t  signature[72];                 /* DER-encoded ECDSA P-256 */
} img_hdr_t;

extern const uint8_t ROOT_PUBKEY_PEM[];     /* in bootloader, write-protected */

bool image_is_trusted(const img_hdr_t *h, const uint8_t *body)
{
    if (h->magic != 0x494D4731u || h->size > APP_MAX_SIZE) return false;
    if (h->version < otp_read_min_version()) return false;      /* anti-rollback */

    uint8_t hash[32];
    mbedtls_sha256(body, h->size, hash, 0);                     /* SHA-256 of image */

    mbedtls_pk_context pk;
    mbedtls_pk_init(&pk);
    bool ok = mbedtls_pk_parse_public_key(&pk, ROOT_PUBKEY_PEM,
                  strlen((const char *)ROOT_PUBKEY_PEM) + 1) == 0
           && mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, hash, sizeof hash,
                  h->signature, sizeof h->signature) == 0;
    mbedtls_pk_free(&pk);
    return ok;                               /* only then: set VTOR, jump to app */
}

Only the public key lives on the device, so extracting it doesn't let an attacker sign firmware. Header fields are validated before use, and the version check prevents downgrading to an old, vulnerable image.

Common mistakes

Applications

Key takeaways

Going further