Bootloader Security: Signature Verification Schemes for Resource-Constrained MCUs
Ed25519 is fast on 32-bit MCUs and simple to implement correctly. RSA-2048 is not. A comparison of signature schemes with measured verification times on Cortex-M4.

For resource-constrained MCUs, Ed25519 verify beats RSA-2048 on speed, code size, and implementation risk. RSA-2048 verify on Cortex-M4 @ 80 MHz took us 580 ms with mbedTLS 3.4.0; Ed25519 verify via monocypher landed at 42 ms. RSA-2048 public key is 256 bytes; Ed25519 public key is 32 bytes. If your threat model allows modern curves, do not default to RSA because AWS IoT examples did.
Threat model (short)
- Attacker can push malicious firmware over UART/USB/OTA
- Attacker cannot extract private signing key from HSM
- Attacker can replay old signed images unless anti-rollback is enforced
Signature scheme choice affects boot latency, flash layout, and audit surface—not just crypto textbook elegance.
Candidates measured
Platform: STM32L476 @ 80 MHz, -Os, thumb2, no hardware crypto accelerator.
| Scheme | Verify time | Flash (verify code) | Pub key size | Notes |
|---|---|---|---|---|
| RSA-2048 PKCS#1 v1.5 | 580 ms | ~28 KB mbedTLS | 256 B | Mature, fat |
| ECDSA P-256 | 95 ms | ~18 KB | 64 B | ASN.1 parsing pain |
| Ed25519 (monocypher) | 42 ms | ~4 KB | 32 B | No ASN.1 |
| Ed25519 (mbedTLS 3.5+) | 48 ms | ~12 KB | 32 B | If already on mbedTLS |
Signing happens offline on CI; verify time dominates boot and OTA user experience.
Why RSA-2048 failed us
Beyond speed: implementing PKCS#1 v1.5 correctly is error-prone; constant-time compare matters for signature bytes. We found a timing side channel in a memcmp early exit during pen test—embarrassing, fixed with mbedtls_ssl_constant_time.
Key rotation with RSA meant embedding multiple 256-byte keys or a certificate chain—flash expensive on 256 KB parts.
Ed25519 integration pattern
[Image header: version | image_size | sig_offset]
[Firmware plaintext...........................]
[Ed25519 signature: 64 bytes]
Public key stored in bootloader RO section or eFuses where available—not in the same flash bank as upgradable app.
// verify sketch — monocypher API
#include "monocypher-ed25519.h"
int verify_image(const uint8_t *fw, size_t fw_len,
const uint8_t sig[64], const uint8_t pk[32]) {
return crypto_ed25519_check(sig, pk, fw, fw_len);
}
Pair with monotonic version counter in OTP or protected EEPROM for rollback defense—signature valid ≠ image current.
MCUboot alignment
We ship MCUboot integration notes separately; scheme choice maps to MCUBOOT_SIGN_EC256 vs custom Ed25519 hook. MCUboot 2.0 ecosystem still skews ECDSA; we added Ed25519 as custom bootutil backend—document the fork.
Full OTA slot geometry lives in OTA update bootloader design.
ECDSA P-256 middle ground
If your compliance checklist names "NIST curves" explicitly, P-256 is reasonable. Budget 100 ms boot verify and test DER signature parsing fuzzing. Hardware accel (STM32 PKA) drops verify to ~12 ms if you enable it—worth it on H7-class.
What I'd do next
- Move signing to cloud HSM (AWS KMS asymmetric) with public key baked at manufacturing—private key never in repo.
- Add fault injection tests—glitch power during verify should fail closed, not skip check.
- Publish verify timing in release notes—when boot goes from 200 ms to 800 ms, support hears about it before engineering does.
Key storage and manufacturing
Production public keys live in bootloader .rodata section programmed once at CM; dev keys never touch factory image. We maintain separate signing/dev and signing/prod keypairs in HSM with ACL—CI prod pipeline uses OIDC to AWS KMS; dev pipeline uses local key in GitHub encrypted secret (rotate quarterly).
Dual-signature window during key rotation: bootloader accepts either old or new public key for 90 days; app image header carries key_id byte. Botched rotation bricked 200 units in a prior life—now automated test asserts both keys verify in HIL before CM flash template update.
Post-quantum planning (minimal)
Compliance asked about PQC roadmap—we documented Ed25519 adequate through product horizon 2032 for our threat model; hybrid ML-DSA signing evaluated for 2027 gateway refresh only. No MCU change today; avoid RSA because "PQC will fix RSA later" is not a plan.
Constant-time verify on M0+
Ed25519 verify at 42 ms on M4; same monocypher build on M0+ at 48 MHz: 380 ms—acceptable for boot once, painful for dual-bank swap validation every OTA chunk. Profile before picking part; PKA-less M0+ may need longer boot SLA negotiation with product.
Audit log export
Regulatory audit requested proof of verify timing under voltage sag—logged verify duration and result code to secure element journal for sample of 1000 OTA attempts. Ed25519 verify remained under 50 ms at 3.0 V; RSA path failed SLA below 3.3 V.
Signature over encrypted payloads
We sign plaintext image before encryption for OTA; verify runs pre-decrypt in RAM-limited staging buffer. Alternative sign-over-ciphertext saves RAM but complicates bit-flip detection—document choice in threat model appendix.
Pick Ed25519 unless regulation forbids it. RSA-2048 on M0+ is a battery and UX tax you do not need in 2024.
Manish Bookreader
Electronics enthusiast, Embedded Systems Expert, Linux/Networking programmer, and Software Engineer passionate about AI, electronics, books, and cooking.

