Status: Accepted · Recorded: 2026-08
Context
Authentication needs AES-128-ECB in exactly one fixed mode and key size, for two things: the 6-byte truncated MAC in challenge-response, and the symmetric obfuscation that carries the system key during pairing.
ESP-IDF already contains mbedTLS, which can do this. But reaching it from an ESPHome external component means depending on how ESPHome's generated src component links mbedTLS — not a declared interface of the framework, and it varies with ESP-IDF version and configuration.
Options considered
- Depend on mbedTLS. Fewer lines to own, but ties the component's cryptography to whatever linkage the surrounding build happens to produce, for one fixed-mode operation. That breaking is a build failure in user configurations, found by users rather than CI.
- Ship a small self-contained AES-128 scoped to the one mode needed.
Decision
Option 2 — a compact FIPS-197 Rijndael implementation in the protocol layer's crypto module, specifically to avoid a build-time dependency on ESP-IDF/mbedTLS linkage from ESPHome's generated src component.
Consequences
- Cryptography no longer depends on the surrounding build's mbedTLS configuration, and builds unchanged in the host test environment where no ESP-IDF exists at all.
- This project owns correctness of that primitive. What actually guards it, strongest first:
- Real captured exchanges (ADR 0010) replayed through the real code: recorded authenticated frames must verify, recorded key-transfers must decrypt to the expected corpus key. This is the load-bearing evidence for the protocol layer — those bytes came off the air, so agreeing with them means agreeing with reality.
- Published NIST/FIPS-197 known-answer vectors for the AES-128 primitive itself — plaintext, key, and ciphertext copied from FIPS-197 Appendix B and NIST SP 800-38A, not generated by this codebase. This is the one check that is independent of everything else here: it cannot pass because two of our own ports agree with each other, only because the primitive matches the published standard.
- Round-trip and negative tests covering encrypt/decrypt symmetry, and confirming a wrong challenge or IV fails rather than silently producing a plausible answer.
- Cross-language vectors pinning C++ against the Python port used by the corpus tooling. These were generated from the same C++ implementation — they prove the two ports agree, not that either is independently correct.
- No general-purpose crypto surface exists here; the module exposes only the protocol's own operations. A future need for another algorithm or mode should be re-evaluated against this decision, not quietly bolted on.