SERIES
Kleidos firmware: three decisions under hard constraints
Three engineering decisions from Kleidos, a hardware password manager built on an ESP32-S3. No operating system to delegate to, no network to phone home to, no filesystem permissions to hide behind, and a flash budget that makes every design choice cost something visible. Read together they show what security engineering looks like when the usual escape hatches are unavailable.
Why these three belong together
Each article isolates one decision and follows it all the way to the measurement that justified it. That is deliberate: on a microcontroller almost every interesting choice is a trade against a fixed budget — flash bytes, RAM, milliseconds, entropy — and a decision defended in the abstract is usually a decision that was never actually paid for. The numbers in these articles are the ones from the build, including the ones that were worse than expected.
The device also makes the threat model unusually concrete. A hardware password manager is a thing an attacker can hold. That single fact removes most of the assumptions general security writing rests on: there is no trusted server to rate-limit an attacker, no account to lock, no operating system keychain to defer to, and no way to keep a secret merely by not writing it down, because the attacker can read the flash. Two of the three articles are direct consequences of that.
The third thread is that none of these decisions were made in isolation from the others. The compact string pool exists partly because the cryptographic work needed the flash it freed. The vault format's authentication step is what makes the key derivation's fail-closed behavior observable rather than theoretical. Reading them in order shows the budget moving from one subsystem to another, which is the part of embedded work that rarely survives into a write-up.
Read in this order
The order runs from the cheapest constraint to understand to the one with the sharpest consequences. Each entry states the decision it settles.
Part 1
What the Linker Won't Do: Packing i18n Strings on an MCU
Start with the flash budget, because it is the constraint that shapes everything after it. A device with a five-language interface stores a lot of short strings, and the naive representation — an array of pointers per language — spends a surprising fraction of its cost on the pointers rather than the text. The article walks through a build-time generator that packs every translation into a single blob addressed by 16-bit offsets, deduplicating the strings that are identical across languages, and reports what that actually saved. It is also the gentlest entry point: no cryptography, just a measurement and a generator.
Part 2
A Vault File That Fails Closed: Encrypt-then-MAC on an MCU
Next, the file the device is for. A vault that decrypts before it authenticates will happily process an attacker's modifications, and on a microcontroller the consequences of parsing attacker-controlled plaintext are not abstract. The article covers encrypt-then-MAC, why the MAC is verified over the ciphertext before a single byte is decrypted, and how the read path is structured so that any failure — truncation, a flipped bit, a substituted file — ends in a refusal rather than a partial result. Fail-closed is a property of the code path, not of an intention, and this is the article that shows the difference.
Part 3
Your 4-Digit PIN Is Fine: Device-Bound Keys on ESP32-S3
Finally, the key that opens it. Users pick a four-digit PIN, and no iteration count saves a four-digit secret from an attacker who has the flash contents and a desktop machine: the entire keyspace is ten thousand candidates. The answer is to make the derivation impossible off-device by mixing in a secret that never leaves it — an eFuse-backed HMAC key the CPU can use but not read — and feeding it through HKDF alongside the PIN. The article explains why this is a device-binding argument rather than a strength argument, and is candid about what it does not protect against.
Where this goes next
The obvious next questions are the ones about the boundary of the device rather than its inside: how firmware updates are authenticated, how the secure boot chain and flash encryption interact with the eFuse secret the third article depends on, and what an attacker with physical access and a laboratory can still recover. Those deserve their own articles rather than a paragraph here, because the honest answers involve limits, not solutions.
Kleidos itself is a private project and its repository is not public, so these articles carry the reasoning and the measurements rather than a link to clone. Everything described is reproducible from the article: the string-pool generator is a build-time script whose algorithm is spelled out, and the cryptographic constructions are standard primitives composed in a stated order, which is the part that matters and the part most often got wrong.
What this series does not cover
There is no hardware chapter — no schematic, no enclosure, no supply-chain discussion — and no user-interface design. The scope is firmware decisions with a security or resource consequence, which is why a display driver and a button debounce routine, both of which took real work, appear nowhere. Nor is this a tutorial series: none of the three articles is a step-by-step build, and following them requires being comfortable with C++ on bare metal and with the vocabulary of applied cryptography.