Skip to main content

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.

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.

Memory map

The EEPROM is organized into a configuration zone (first ~220 bytes) and a credential zone (remainder):
AddressSizeContentSource in code
0x00001 BConfig wizard flag (0x42 = done)zerokey-setup.cpp
0x00011 BScreen orientation (0 = normal, 1 = inverted)EEPROM_SCREEN_MODE_ADDR
0x00021 BSoft failed-attempts counter (UX backoff)FAILED_ATTEMPTS_ADDR
0x0010–0x001F16 BAES-CBC Initialization VectorEEPROM_IV_ADDR
0x0020–0x00234 BPIN attempt threshold (LE uint32)EEPROM_THRESHOLD_ADDR
0x00241 BProvisioning flag (0xA5 = provisioned)EEPROM_PROVISION_FLAG
0x0028–0x003716 BAES-128 master key (TRNG-generated)EEPROM_AES_MASTER
0x0038–0x005732 BPIN hash: SHA-256(PIN ∥ serial)EEPROM_PIN_HASH
0x003E1 BKeyboard layout selector (0–8)EEPROM_LAYOUT_ADDR
0x0040–0x00478 BLast TOTP epoch (big-endian)EEPROM_LAST_TOTP_EPOCH_ADDR
0x0060–0x00DB124 BTOTP metadata: 2 B × 62 slots (algorithm + secret_len)CONFIG_TOTP_META_START
0x00E0–0x1FFF7 936 B62 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
There is no separate status byte or CRC per page. Empty slots are written as encrypted 0xFF blanks during silentEraseAll().

Page address calculation

credentialPageAddress(slotIndex, pageIndex) =
    (FIRST_CREDENTIAL_PAGE + slotIndex × 4 + pageIndex) × 32
Where:
  • FIRST_CREDENTIAL_PAGE is computed from CONFIG_TOTP_META_END rounded up to the next 32-byte boundary.
  • slotIndex ranges from 0 to 61.
  • pageIndex ranges from 0 (site) to 3 (TOTP).

Write sequence

The writeEepromPage() function writes 32 bytes at a page-aligned address:
  1. Check EEPROM presence via Wire.beginTransmission() + ACK test.
  2. Send 2-byte address (MSB first) followed by 32 data bytes.
  3. Wait 10 ms for the EEPROM internal write cycle.
  4. Return true if Wire.endTransmission() reported no error.
For security-related writes that cross page boundaries (e.g., 16-byte IV, 32-byte PIN hash), 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:
  1. Send 2-byte address via I²C write.
  2. Issue Wire.requestFrom(eepromAddress, 32).
  3. Read all available bytes into the output buffer.
  4. 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:
ByteContent
0Algorithm code: 0 = none, 1 = SHA-1, 2 = SHA-256, 3 = SHA-512
1Secret length in raw bytes (before Base32 encoding)
Metadata is read/written independently of credential pages to allow quick TOTP detection without decrypting the entire slot.

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 0x0040 is 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

SymptomCauseFix
EEPROM not foundI²C connection brokenCheck solder joints; verify pull-ups on SDA/SCL
EEPROM write 0xNNNWrite failed at addressRe-try; if persistent, EEPROM may be damaged
Garbled credentialsIV or AES key changed without re-encryptionFactory reset + re-provision; restore from backup
Slot shows blank after importTOTP secret parsing failedCheck 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.