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 uses a Master PIN (1–16 digits) to authenticate the user. The verification process combines a software constant-time hash comparison with a hardware monotonic counter in the ATECC608A to block brute-force attempts at both software and hardware levels.

How the PIN is stored

The PIN is never stored in plaintext. At PIN setup time (storeSignature()):
  1. The user’s PIN digits are read from pinArray[16] (each byte holds a digit value 0–9).
  2. derivePinKey() computes: SHA-256(pinArray[16] ∥ chip_serial[9]).
    • chip_serial is the 9-byte unique serial read from the ATECC608A Config Zone.
  3. The resulting 32-byte hash is written to EEPROM at 0x0038–0x0057.
  4. The same 32-byte hash is also written to ATECC slot 9 (for potential future CheckMac use).
The chip serial acts as a hardware salt: the same numeric PIN on a different device produces a completely different 32-byte hash.

Unlock sequence

Each unlock attempt executes the following steps in verifySignature():
1. zerokeyAtecc.counterIncrement(Counter0) → new_counter
2. Read threshold from EEPROM [0x0020]
3. If new_counter >= threshold → eraseAll() + halt (hard lockout)
4. derivePinKey(pinArray, derived):
       serial = ATECC608A.readSerial()
       derived = SHA-256(pinArray[16] || serial[9])
5. Read stored hash from EEPROM [0x0038] → stored[32]
6. diff = 0; for i in 0..31: diff |= stored[i] ^ derived[i]  // constant-time
7. If diff == 0:
       writeThreshold(new_counter + 50)   // reset budget
       writeFailedAttemptsCounter(0)
       → ACCESS GRANTED
8. Else:
       incrementFailedAttemptsCounter()
       waitFromEeprom()                   // exponential backoff
       → ACCESS DENIED

Hardware counter — hard limit

Counter0 in the ATECC608A is a monotonic hardware counter. Its key properties:
  • Increments by 1 on every counterIncrement() call — once per PIN attempt.
  • Cannot be decremented, reset, or rolled back by any software, power cycle, or hardware reset.
  • Persists across power loss inside the chip’s non-volatile memory.
  • The threshold stored at EEPROM 0x0020 is a uint32 (little-endian) representing the counter value at which the next wipe occurs.
EventCounter effectThreshold effect
Wrong PIN attempt+1unchanged
Correct PIN+1reset to Counter0 + 50
Counter ≥ thresholderaseAll() triggered
With an uninterrupted stream of wrong PINs the counter catches up to the threshold in exactly 50 attempts and triggers a full credential wipe.

Adaptive delays — soft backoff

Stored at EEPROM 0x0002. This UX-layer counter is reset on a correct PIN:
Failed attemptsWait time
0none
15 s
210 s
320 s
440 s
580 s
6160 s
7320 s
8640 s
91 280 s
≥ 102 560 s (≈ 43 min)
Formula: wait = 5 × 2^(min(attempts, 10) − 1) seconds, capped at 2 560 s. During the delay the OLED shows a progress bar and countdown. The device does not accept new input until the timer expires.

Secure input handling

  • Digits are buffered in pinArray[16] in SRAM and cleared after verification.
  • Touch events are ignored during the lockout wait (waitFromEeprom()).
  • SerialUSB cannot inject PIN digits — only physical capacitive-touch input is accepted.
  • The PIN comparison uses a constant-time XOR accumulator (diff |= stored[i] ^ derived[i]) to avoid timing side-channels.

Changing the PIN

Initiated via Menu → Change PINstoreSignature():
  1. 3-second on-screen countdown (allows safe abort).
  2. derivePinKey(pinArray, derived) computes the new hash.
  3. New 32-byte hash written to ATECC slot 9.
  4. New 32-byte hash written to EEPROM 0x0038.
  5. Current Counter0 is read; threshold is set to Counter0 + 50.
  6. AES master key is checked — if already present, it is not replaced. The AES key is independent of the PIN.
  7. IV is loaded or generated.
  8. Setup config flag is written (0x42).
  9. All credential slots are silently re-initialised with encrypted blanks.
Changing the PIN does not change the AES master key or re-encrypt existing credentials. Credentials encrypted under the old key remain readable as long as the AES master at 0x0028 has not changed.

Forgotten PIN

ZeroKeyUSB has no PIN recovery mechanism. The only option is a factory reset (eraseAll()), which:
  1. Shows a 3-second countdown.
  2. Loads the device IV.
  3. Overwrites all 62 credential slots × 4 pages with encrypted blanks.
  4. Clears TOTP metadata.
After reset the device halts with a “LOCKED — reflash” error. The bootloader must be used to flash new firmware and re-provision the device from scratch. Previously stored credentials are unrecoverable unless you have a plaintext backup exported before the reset.
Choose a PIN you can remember but others cannot guess. A PIN of 4 digits or fewer is vulnerable to offline SHA-256 dictionary attacks if an adversary gains I²C access to the device.