ZeroKeyUSB stores every secret inside an external ST M24C64-WMN6TP EEPROM (64 Kbit = 8 KB). The firmware manages reads and writes carefully, respecting page boundaries and keeping all credential data encrypted at rest.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.
Memory map
The EEPROM is organized into a configuration zone (first ~220 bytes) and a credential zone (remainder):| Address | Size | Content | Source in code |
|---|---|---|---|
0x0000 | 1 B | Config wizard flag (0x42 = done) | zerokey-setup.cpp |
0x0001 | 1 B | Screen orientation (0 = normal, 1 = inverted) | EEPROM_SCREEN_MODE_ADDR |
0x0002 | 1 B | Soft failed-attempts counter (UX backoff) | FAILED_ATTEMPTS_ADDR |
0x0010–0x001F | 16 B | AES-CBC Initialization Vector | EEPROM_IV_ADDR |
0x0020–0x0023 | 4 B | PIN attempt threshold (LE uint32) | EEPROM_THRESHOLD_ADDR |
0x0024 | 1 B | Provisioning flag (0xA5 = provisioned) | EEPROM_PROVISION_FLAG |
0x0028–0x0037 | 16 B | AES-128 master key (TRNG-generated) | EEPROM_AES_MASTER |
0x0038–0x0057 | 32 B | PIN hash: SHA-256(PIN ∥ serial) | EEPROM_PIN_HASH |
0x003E | 1 B | Keyboard layout selector (0–8) | EEPROM_LAYOUT_ADDR |
0x0040–0x0047 | 8 B | Last TOTP epoch (big-endian) | EEPROM_LAST_TOTP_EPOCH_ADDR |
0x0060–0x00DB | 124 B | TOTP metadata: 2 B × 62 slots (algorithm + secret_len) | CONFIG_TOTP_META_START |
0x00E0–0x1FFF | 7 936 B | 62 credential slots (128 B each = 4 pages × 32 B) | EEPROM_CREDENTIAL_BASE |
Credential slot structure
Each of the 62 credential slots occupies 4 consecutive 32-byte EEPROM pages (128 bytes total): Each 32-byte page contains:- 16 bytes of plaintext (padded with
0xFF) encrypted as two AES-128 CBC blocks - The encryption uses the device-wide AES master key and IV
status byte or CRC per page. Empty slots are written as encrypted 0xFF blanks during silentEraseAll().
Page address calculation
FIRST_CREDENTIAL_PAGEis computed fromCONFIG_TOTP_META_ENDrounded up to the next 32-byte boundary.slotIndexranges from 0 to 61.pageIndexranges from 0 (site) to 3 (TOTP).
Write sequence
ThewriteEepromPage() function writes 32 bytes at a page-aligned address:
- Check EEPROM presence via
Wire.beginTransmission()+ ACK test. - Send 2-byte address (MSB first) followed by 32 data bytes.
- Wait 10 ms for the EEPROM internal write cycle.
- Return
trueifWire.endTransmission()reported no error.
eepromWriteRaw() in zerokey-security.cpp splits the data at 32-byte page boundaries to avoid the M24C64’s address wrap-around behavior.
Read sequence
readEepromPage() reads exactly 32 bytes:
- Send 2-byte address via I²C write.
- Issue
Wire.requestFrom(eepromAddress, 32). - Read all available bytes into the output buffer.
- Zero-fill any bytes not received (partial read = error).
TOTP metadata
Each credential slot has a 2-byte TOTP metadata entry in the config zone:| Byte | Content |
|---|---|
| 0 | Algorithm code: 0 = none, 1 = SHA-1, 2 = SHA-256, 3 = SHA-512 |
| 1 | Secret length in raw bytes (before Base32 encoding) |
Wear characteristics
- The M24C64-WMN6TP supports >1 million write cycles per page (datasheet guarantee).
- Credential pages are only rewritten when the user edits a field or imports data.
- Config zone pages (IV, PIN hash, threshold) are written during provisioning and PIN changes — infrequent events.
- The TOTP epoch at
0x0040is updated each time the user syncs time or generates a code — this is the most-written location. - Because credentials are typically static, expected EEPROM lifespan exceeds decades of normal use.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
EEPROM not found | I²C connection broken | Check solder joints; verify pull-ups on SDA/SCL |
EEPROM write 0xNNN | Write failed at address | Re-try; if persistent, EEPROM may be damaged |
| Garbled credentials | IV or AES key changed without re-encryption | Factory reset + re-provision; restore from backup |
| Slot shows blank after import | TOTP secret parsing failed | Check Base32 encoding; verify algorithm support |
No decrypted credential ever touches persistent memory without explicit user action. Plaintext exists only in SRAM during the active session.