How ZeroKeyUSB protects your credentials through a hardware secure element, AES-128 CBC encryption (key on chip, ECB blocks in hardware), and offline-only operation.
ZeroKeyUSB does not rely on the Internet, cloud storage, or companion apps.
Everything — from random number generation to PIN verification — happens inside the device, powered directly through USB.Your passwords never leave the hardware and cannot be accessed remotely, even by the manufacturer.
Security is split across two pieces of silicon so neither one alone can leak the vault:
Chip
Role
SAMD21E18A (MCU)
Runs the application firmware, manages the CBC chaining logic, drives the OLED, USB HID, touch, and I²C bus. Single-block AES is delegated to the secure element.
ATECC608A-MAHDA-T (secure element)
Generates true random numbers (TRNG), holds a unique 9-byte chip serial used as PIN salt, provides a tamper-resistant monotonic hardware counter (Counter0), and — once provisioned — performs every AES-128 ECB block in dedicated hardware using a key stored in slot 8 that never leaves the chip.
The MCU and ATECC608A share an I²C bus at address 0x60. The MCU does not have a usable copy of the AES key: it sends 16-byte plaintext blocks to the chip and receives 16-byte ciphertext back. The chip generated the key itself at provisioning time using its TRNG; the byte sequence never crossed the I²C bus.
Crypto model — Camino A (AES key + AES engine on chip).
The firmware enables the hardware AES command, configures slot 8 as an AES key holder (IsSecret=1, KeyType=6), writes a 16-byte TRNG-generated key to it, and locks both Config and Data zones. From that point on, encryption and decryption are single-block ECB calls to the chip, chained on the MCU into CBC.
All sensitive data is stored in the external EEPROM M24C64-WMN6TP, encrypted using AES-128 in CBC mode. Each ECB block is computed by the ATECC608A hardware AES engine; the MCU handles only the CBC XOR chaining.
Element
Source / Location
Cipher
AES-128 CBC — single-block ECB delegated to the ATECC608A AES command (opcode 0x51). Per-block XOR chaining is done on the MCU around the chip calls so the key never has to be loaded into MCU SRAM.
AES master key
16 random bytes produced by the ATECC608A TRNG at first boot and written to slot 8 of the chip. IsSecret=1 means it can never be read out via the I²C bus.
IV (Initialization Vector)
16 random bytes produced by the ATECC608A TRNG at provisioning. Stored in EEPROM at 0x0010–0x001F.
PIN binding
The AES master key is not derived from the PIN. The PIN is verified separately via an EEPROM-stored hash and gates access to the unlock routine.
Credential layout
Each slot holds up to four 32-byte encrypted EEPROM pages: site (p0), username (p1), password (p2), TOTP secret (p3). Each 32-byte page encrypts 16 bytes of plaintext padded to 32 bytes with 0xFF.
cbcEncrypt32 / cbcDecrypt32 process each 32-byte credential in two 16-byte blocks. For each block:
The plaintext block is XORed with the previous ciphertext (or with the device IV for the first block).
The XOR result is sent to the ATECC608A via the AES command (mode=0x00 for encrypt, 0x01 for decrypt, key from slot 8, key block 0). The chip returns the 16-byte ciphertext.
The resulting ciphertext becomes prev for the next block.
Decryption is symmetric: the chip returns plaintext, the MCU XORs it with the previous ciphertext to recover the original block.Why split the work this way? The ATECC608A’s AES command exposes only single-block ECB. CBC is the chaining policy layered on top — implementing it on the MCU keeps every byte of the key inside the secure element while giving us the diffusion benefits of CBC on the credential data.
Credential pages (4 × 32 B × 62 slots = 7936 B max)
Note on 0x0028. Older units (compiled before the AES move) used this region to hold the 16-byte AES master in plaintext. New units do not touch it; the bytes remain at whatever the EEPROM had previously. Treat the address as reserved.
The PIN authorises an unlock cycle; it is never used directly as an encryption key.The verification flow:
The user enters up to 16 digits on the capacitive pads.
The firmware increments Counter0 in the ATECC608A. This counter is monotonic and survives power cycles — it cannot be rolled back by software or by desoldering the MCU.
The firmware reads the attempt threshold from EEPROM (0x0020). If Counter0 ≥ threshold, the device calls eraseAll() (wipes all credential slots) and halts.
Otherwise, the firmware calls derivePinKey(): computes SHA-256(pinArray[16] ∥ chip_serial[9]) to produce the 32-byte PIN hash.
It reads the stored 32-byte hash from EEPROM (0x0048) and runs a constant-time compare (diff |= stored[i] ^ derived[i]).
On match: the threshold is reset to Counter0 + 50, the soft failed-attempt counter is cleared, and the unlock proceeds.
On mismatch: the soft counter increments and the device enforces an exponential backoff delay before the next attempt.
Because the rate-limit counter is in hardware, an attacker who dumps EEPROM still cannot brute-force the PIN against the device — Counter0 keeps marching forward with each attempt and the firmware wipes the vault once the threshold is crossed.
zerokeyAtecc.provisionAesAndLock() runs once, before the setup wizard:
Read Config Zone blocks 0, 1 and 3 to learn the chip’s current factory values.
Set the AES_Enable bit (byte 13, bit 0) using a 32-byte block write that preserves every factory bit we didn’t intend to change. Re-read and verify the bit took effect; abort without locking if not.
Set SlotConfig[8] — IsSecret=1 (bit 7 of byte 36) and WriteConfig=Never (high nibble of byte 37 = 0x4), preserving the rest of the byte. Re-read and verify.
Set KeyConfig[8].KeyType = 6 (AES) — bits 2..4 of byte 112, preserving every other bit. Re-read and verify.
Lock the Config zone. Irreversible.
Generate a 16-byte random key via the chip’s TRNG and write it to slot 8 in the clear (still allowed while the data zone is open).
Lock the Data zone. Irreversible.
Every write is followed by a read-back. If any verify fails, the function returns a numbered PROV E<n> error with the chip’s raw status byte attached and does not proceed to lock the zone, so a misbehaving chip cannot brick itself silently.
Why bit-level writes? The MAHDA-T parts ship with several “reserved” bits in byte 13 (AES_Enable) factory-set. A naive write that clears them is rejected by the chip with a parse error (SS=0x03). The provisioning code reads each byte first, OR-masks only the bits it actually needs to flip, and writes the whole 32-byte block back.
Known trade-off (Slot 9 readable): Slot 9 is not locked as secret because the MAHDA-T SKU rejects clear writes to IsSecret slots 0–7. The PIN hash lives there with IsSecret=0, so an attacker with physical I²C access can read the 32-byte hash and attempt an offline SHA-256(PIN∥serial) brute force. Counter0 hardware lockout limits online attempts but not offline cracking. Short PINs are vulnerable to this attack — use the maximum 16 digits.
The IV is generated once during provisioning by the ATECC608A’s TRNG and stored in EEPROM at 0x0010. Two sanity guards protect it:
A read returning all-0x00 or all-0xFF is treated as uninitialised and triggers regeneration from the TRNG.
If EEPROM read fails at unlock time, the firmware attempts TRNG regeneration and re-stores the IV.
Single device-wide IV: all credential pages are chained against the same IV. This keeps the layout simple and auditable. The threat model leans on TRNG quality and Counter0, not on per-record nonces.Regeneration consequence: if the IV is lost or regenerated without re-encrypting credentials, existing slots will decrypt to garbage (the ciphertext was produced under the old IV). The firmware’s self-healing routine (silentEraseAll) is called automatically on first unlock if slot 0 page 0 is still raw 0xFF (EEPROM default), and can be called again manually via generateAndStoreIV().
On the first unlock after provisioning, ZerokeySecurity::unlock() checks whether credential slot 0, page 0 is still at the EEPROM factory default (0xFF across all 32 bytes). If so, it calls silentEraseAll():
Loads the device IV from EEPROM.
For each of the 62 credential slots × 4 pages: encrypts a 32-byte 0xFF blank under AES-128 CBC and writes it to EEPROM.
Clears TOTP metadata for every slot.
This ensures fresh units always have consistent, properly encrypted blank entries before any credential is written.
Splitting fields keeps recognisable plaintext patterns out of the ciphertext stream and limits the blast radius of a corrupt EEPROM page. Padding bytes are 0xFF; trailing 0x20 (space) chars are replaced with 0xFF before encryption to avoid pattern leakage.
The PCB is encapsulated in epoxy resin; opening the device destroys the board and the chip connections.
No wireless interfaces (no Wi-Fi, no Bluetooth, no NFC).
The bootloader region is BOOTPROT-locked in hardware fuses (BOOTPROT = 7, protecting the first 16 KB) — application firmware cannot rewrite or relocate the bootloader.
The bootloader will only jump to ECDSA P-256-signed firmware; an unsigned or tampered image falls into USB-CDC recovery mode instead of executing.
All decrypted credentials live in temporary RAM buffers (currentSite, currentUser, currentPass) that are populated at unlock and overwritten at the next lock or power cycle.
Write-protect pin (EEPROM_WP_PIN = PA01) can be driven high by firmware to hardware-lock EEPROM writes.
Credentials can be exported and imported over the USB CDC serial interface:
Export (backupAllCredentials): decrypts all 62 slots in-device and sends them as comma-delimited plaintext lines over SerialUSB. The host receives credentials in clear — ensure the USB connection is trusted.
Import (loadAllbackupCredentials): receives records from the host, re-encrypts them under the current device IV/master, and writes them to EEPROM. TOTP secrets are parsed and stored in page 3 of each slot.
Security note: backup transmits decrypted credentials in plaintext over USB. Only perform backup/restore on a trusted, air-gapped host.
ZeroKeyUSB’s firmware is fully open-source and available for public audit and verification. Anyone can review:
How the ATECC608A is driven, including the bit-level provisioning routine that enables the chip’s AES engine and locks both zones (zerokey-atecc.cpp).
How CBC chaining wraps the chip’s single-block AES command (zerokey-security.cpp).
How the IV is generated, validated, and refreshed (zerokey-security.cpp::generateAndStoreIV).
How the bootloader hashes and verifies the application (bootloader/src/main.c).
There are no remote update mechanisms: re-flashing requires physical access via SWD pogo pins or the local USB-CDC bootloader, and any new image must be signed by the offline ECDSA key.
How the ATECC608A’s hardware AES engine encrypts every credential block, with CBC chaining wrapped around it on the MCU.
PIN Verification
Counter0, the EEPROM-stored SHA-256 hash, and the constant-time compare that gates unlock.
IV Generation
How the ATECC608A TRNG seeds the device-wide IV and how regeneration is handled.
ZeroKeyUSB pairs an MCU with a hardened secure element. The chip provides the entropy (TRNG), the identity (chip serial used as PIN salt), the rate limit (Counter0), and the cipher itself — every AES block is computed inside the secure element using a key the MCU has never seen. The MCU’s role is to chain those blocks into CBC, drive the UI, and shuttle plaintext / ciphertext to and from the EEPROM.