Skip to main content
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 0x0048–0x0067.
  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 [0x0048] → 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 0x0048.
  5. Current Counter0 is read; threshold is set to Counter0 + 50.
  6. ATECC ping confirms the chip is still alive. The AES key in slot 8 is not touched by PIN setup — it is provisioned once at first boot and is irrevocable.
  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. The AES key lives inside ATECC slot 8 and is generated once per device; it cannot be rotated. Existing ciphertext is decryptable with the same chip as long as it is not destroyed.

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.