ZeroKeyUSB encrypts each credential block using AES-128 in CBC mode, implemented in software on the SAMD21 Cortex-M0+ MCU using the AESLib library.Documentation Index
Fetch the complete documentation index at: https://docs.zerokeyusb.com/llms.txt
Use this file to discover all available pages before exploring further.
Key material
The AES master key is a 16-byte random value generated by the ATECC608A TRNG at provisioning time. It is not derived from the PIN.| Property | Detail |
|---|---|
| Source | ATECC608A hardware TRNG (random() command, mode 0x00 — updates seed before generating) |
| Size | 16 bytes (128 bits) |
| EEPROM location | 0x0028 – 0x0037 |
| RAM cache | Loaded into a static byte aes_master[16] on first use; flag aes_master_loaded prevents redundant EEPROM reads |
| Validity check | Rejected if all-0x00 or all-0xFF (indicates uninitialised EEPROM) |
Why software AES?
The ATECC608A variant used (MAHDA-T) ships with the hardware AES command disabled at factory. As a result, AES encryption/decryption runs entirely on the MCU via the AESLib library. The ATECC is still used for:
- Generating the random AES key and IV (TRNG).
- Hardware PIN attempt rate-limiting (Counter0).
- PIN key derivation salt (chip serial).
CBC chaining implementation
ThecbcEncrypt32 / cbcDecrypt32 functions in zerokey-security.cpp process each 32-byte credential field (two 16-byte blocks) as follows:
Encryption
Decryption
zero_iv to AESLib? AESLib’s CBC API writes back to the IV argument at runtime. If the IV were stored in flash (a static const), the write would trigger a HardFault on SAMD21’s Cortex-M0+. By manually applying the XOR before calling AESLib, the library call reduces to a single ECB block with a zero IV — functionally identical to CBC with correct chaining.
Padding
Each credential field (site, username, password) is 16 bytes in RAM. Before encryption:- Trailing space characters (
0x20) are replaced with0xFFfrom the end inward. - The 16-byte field is placed in a 32-byte buffer; the upper 16 bytes are filled with
0xFF.
bufferToString() strips 0xFF bytes and reads until \0 or 0xFF.
Per-operation flow
lock() — encrypt and write credentials
- Replace trailing spaces in
currentSite,currentUser,currentPasswith0xFF. - Load IV from EEPROM (
loadIVfromEEPROM()). - For each of the 3 fields:
- Copy 16 bytes into a 32-byte buffer, pad upper half with
0xFF. - Call
cbcEncrypt32(iv, plain, encrypted). - Write 32-byte ciphertext to the correct EEPROM page.
- Copy 16 bytes into a 32-byte buffer, pad upper half with
unlock() — decrypt and load credentials
- Load IV from EEPROM.
- Self-heal check: if slot 0 page 0 is raw
0xFF, callsilentEraseAll(). - For each of the 3 fields:
- Read 32-byte ciphertext from EEPROM.
- Call
cbcDecrypt32(iv, encrypted, decrypted). - Copy first 16 bytes into
currentSite/currentUser/currentPass.
Security considerations
| Consideration | Status |
|---|---|
| Key entropy | 16 bytes = 128 bits from hardware TRNG — not brute-forceable |
| PIN ≠ key | Changing or forgetting the PIN does not affect the AES key or existing ciphertext |
| Key at rest | AES master stored in plaintext EEPROM at 0x0028 — readable by an attacker with I²C access |
| Physical protection | PCB is epoxy-encapsulated; accessing I²C requires destroying the device |
| Factory reset | eraseAll() overwrites all credential pages with encrypted blanks; the AES key and IV remain in EEPROM until a full re-provisioning |
| No key escrow | There is no backup copy of the AES master key; EEPROM destruction = permanent data loss |