Developing a Bootloader: Design Considerations, Code Signing, and Encryption

What it takes to design a reliable embedded bootloader, from fail-safe updates and memory layout to digital signatures, secure boot, and firmware encryption.

A bootloader is the first code that runs on your device, and on many products it is the only thing standing between a routine field update and an expensive on-site repair. Done well, nobody ever notices it. Done badly, it bricks a batch of devices in the field, or worse, lets someone else load their own firmware onto your product. Teams often treat the bootloader as a quick utility to write at the end of the project, but it deserves the same design discipline as the application it protects, particularly for medical, industrial, and other regulated products where updates must be both reliable and trustworthy.

What a Bootloader Actually Has to Do

At its simplest, a bootloader starts after reset, decides whether the application in flash is good, and jumps to it. On a typical ARM Cortex-M part that jump involves more detail than it sounds like: relocating the vector table, loading the application's initial stack pointer, de-initializing any peripherals and interrupts the bootloader enabled, and then branching to the application's reset handler. Skipping any of these produces the classic symptom of a bootloader that works on the bench but hangs unpredictably in production.

Beyond starting the application, a bootloader usually also has to:

  • Receive new firmware over whatever link the product has, such as UART, USB, CAN, Ethernet, BLE, or a cellular modem.
  • Write it to flash safely, respecting sector sizes, alignment, and erase times.
  • Validate the image before trusting it, and decide what to do when validation fails.
  • Report its own version and status so a service tool or cloud backend knows what is on the device.

Design Decisions to Make Early

Several choices are hard to change once units ship, so they belong in the architecture phase rather than the bring-up phase.

  • Memory map. Decide how much flash the bootloader gets, where the application starts, and where configuration data lives. Allow room for the bootloader to grow, because a bootloader that outgrows its region forces a full re-layout of every deployed product.
  • Update transport. The bootloader needs its own minimal driver and protocol stack, which adds code, and every line of that code is attack surface. Keep it small and well tested.
  • Entry conditions. Define exactly how the device enters update mode: a button held at power-up, a flag set by the application, a missing or invalid image, or a command from a host. Make sure an application crash can never lock out the update path.
  • Self-update. Decide whether the bootloader itself can ever be updated. Updating the code that runs first is the riskiest operation on the device, so many designs make the first stage immutable or protect it with hardware write protection and put anything that needs to change in a second stage.
  • Watchdog and fault handling. The bootloader has to behave sensibly when the application resets repeatedly, and it needs a watchdog strategy that does not interfere with long flash erase cycles.

Making Updates Fail-Safe

The defining requirement of a production bootloader is that power can be lost at any instant during an update and the device must still recover. A battery pulled out, a USB cable yanked, or a brownout during a flash erase should leave the product able to try again, not sitting dead.

The strongest approach is dual-bank (or A/B) storage: the new image is written into an inactive slot while the old one stays intact, and the bootloader only switches over after the new image has been fully written and verified. If the new firmware fails to boot, a confirmation timeout or watchdog reset lets the bootloader revert to the previous slot. When flash is too small for two full images, a single-bank design with a swap area or a small recovery loader can work, but you give up some of the safety margin and must test power-loss behavior much more aggressively. Whatever the approach, an update should be an atomic event from the device's point of view, and the test plan should include deliberately cutting power at random points thousands of times.

Signing: Proving the Firmware Is Yours

A CRC or checksum protects against corrupted downloads, not against attackers. Anyone who can recompute a CRC can load modified firmware. To ensure the device only runs code you authorized, the image must be digitally signed and the bootloader must verify that signature.

The standard pattern looks like this:

  • At build time, compute a cryptographic hash (SHA-256 is typical) over the firmware image and its header, then sign that hash with a private key using an asymmetric algorithm such as ECDSA P-256 or Ed25519.
  • The matching public key is stored in the bootloader, in a region that cannot be modified in the field. The private key never leaves your signing infrastructure.
  • Before running or installing an image, the bootloader recomputes the hash and verifies the signature. If either step fails, the image is rejected and the device stays on its current firmware.

The details are where products succeed or fail:

  • Protect the private key. Keep it in a hardware security module or an equivalent controlled signing service, with access logging and a documented release process. A leaked signing key defeats the entire scheme.
  • Sign the header too. Version numbers, image length, and target hardware identifiers must be covered by the signature, or an attacker can alter them without invalidating it.
  • Add rollback protection. Without a monotonic version counter, an attacker can install an older, validly signed image that has a known vulnerability. Store a minimum allowed version in OTP fuses or protected storage and refuse anything lower.
  • Verify what you run. Check the image after it is fully in flash rather than only while streaming in, and beware of checks that can be bypassed between verification and execution (a time-of-check to time-of-use gap).
  • Plan for key revocation and rotation. Support more than one public key slot so a compromised or expiring key can be retired without recalling hardware.

Secure boot extends this into a chain of trust. A ROM or immutable first stage verifies the bootloader, the bootloader verifies the application, and each link only hands control to code the previous link has authenticated. Many modern microcontrollers provide hardware support for this, including immutable boot ROM, fuse-based key storage, and hardware crypto accelerators, and using it is far stronger than a purely software implementation.

Encryption: Keeping the Firmware Confidential

Signing answers the question "is this firmware authentic?" Encryption answers a different one: "can someone read it?" They solve different problems, and a secure design usually needs both. Encryption matters when your firmware contains valuable intellectual property, proprietary algorithms, or embedded secrets, or when you ship updates over a network where the image could be captured and cloned.

  • Use authenticated encryption or encrypt-then-sign. Encryption alone does not prove an image was not tampered with. Modes such as AES-GCM, or AES-CTR combined with a signature, keep confidentiality and integrity together.
  • Decide on key scope. A single fleet-wide key is simple but means one extracted key exposes every device. Per-device keys limit the damage but require provisioning at manufacturing and a way to deliver individually encrypted images.
  • Store decryption keys in protected hardware. Secure elements, dedicated key storage, or read-protected flash keep the key out of reach. A key sitting in ordinary flash can be read out by anyone with a debugger.
  • Decrypt on the fly. Where possible, decrypt as the image is written rather than storing plaintext in a staging area that could be dumped.

The Rest of the Attack Surface

Strong cryptography can be undone by weak hardware configuration. Disable or lock the debug port (SWD/JTAG) in production, enable the part's readout protection, and write-protect the bootloader region. Consider fault-injection attacks such as voltage or clock glitching that try to skip the signature check, and use defensive coding techniques like redundant checks and randomized delays where the threat model justifies it. Finally, use proven libraries and reference designs such as MCUboot, wolfBoot, or vendor secure boot solutions instead of writing cryptographic primitives from scratch.

For medical device manufacturers there is also a regulatory dimension. FDA expects cybersecurity to be designed into the product, including a secure and documented update mechanism, and standards such as IEC 62304 call for controlled software release and maintenance processes. A signed, rollback-protected, fail-safe update path directly supports those expectations, and the bootloader design, threat model, and verification evidence should all be part of the design history.

Getting It Right the First Time

A bootloader is a small piece of code with an outsized impact on reliability, security, and your ability to support a product for years after launch. The right time to design it is at the start of the project, alongside the hardware choices that make secure boot possible. If you are planning a new product, or you need to add secure updates to an existing one, contact SiGenix to talk through your requirements with our embedded firmware engineers.

Written by

SiGenix Engineering

Pittsburgh-based electronics and medical device engineering firm. Our president holds 14 granted U.S. patents spanning medical device monitoring, therapeutic systems, and patient compliance technology.