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.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.
How the PIN is stored
The PIN is never stored in plaintext. At PIN setup time (storeSignature()):
- The user’s PIN digits are read from
pinArray[16](each byte holds a digit value 0–9). derivePinKey()computes:SHA-256(pinArray[16] ∥ chip_serial[9]).chip_serialis the 9-byte unique serial read from the ATECC608A Config Zone.
- The resulting 32-byte hash is written to EEPROM at
0x0038–0x0057. - The same 32-byte hash is also written to ATECC slot 9 (for potential future CheckMac use).
Unlock sequence
Each unlock attempt executes the following steps inverifySignature():
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
0x0020is a uint32 (little-endian) representing the counter value at which the next wipe occurs.
| Event | Counter effect | Threshold effect |
|---|---|---|
| Wrong PIN attempt | +1 | unchanged |
| Correct PIN | +1 | reset to Counter0 + 50 |
| Counter ≥ threshold | — | eraseAll() triggered |
Adaptive delays — soft backoff
Stored at EEPROM0x0002. This UX-layer counter is reset on a correct PIN:
| Failed attempts | Wait time |
|---|---|
| 0 | none |
| 1 | 5 s |
| 2 | 10 s |
| 3 | 20 s |
| 4 | 40 s |
| 5 | 80 s |
| 6 | 160 s |
| 7 | 320 s |
| 8 | 640 s |
| 9 | 1 280 s |
| ≥ 10 | 2 560 s (≈ 43 min) |
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 PIN →storeSignature():
- 3-second on-screen countdown (allows safe abort).
derivePinKey(pinArray, derived)computes the new hash.- New 32-byte hash written to ATECC slot 9.
- New 32-byte hash written to EEPROM
0x0038. - Current Counter0 is read; threshold is set to
Counter0 + 50. - AES master key is checked — if already present, it is not replaced. The AES key is independent of the PIN.
- IV is loaded or generated.
- Setup config flag is written (
0x42). - 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:
- Shows a 3-second countdown.
- Loads the device IV.
- Overwrites all 62 credential slots × 4 pages with encrypted blanks.
- Clears TOTP metadata.