High-Level Design Docs (CS)

High-level description of how the SafeKey is designed and how it works.


SafeTech's SafeKey has been designed as a long-term (10+ years) external, low-capacity encrypted storage device for sensitive data, which is not be available freely on the PC’s hard drive.

Its interface is available over-browser for ease-of-use and is able support multiple domains, but not cross-site access. It allows for the safe store at least 350 bytes of data for each record and backup of the data

SafeKey contains additional features similar to a key-value store (no rigid structure for data, except for the ID field). It allows to store 80 data records of up to 480 bytes capacity (including the ID field, and small storage structure overhead).

The Custom Storage (CS) is encrypted with AES256, and accessible only after providing correct PIN. For usability, the CS’ PIN is shared with FIDO2 PIN application.

Data are stored as received from the JavaScript application, encoded inside a CBOR structure.

For backward-compatibility, and to allow for communication over any Internet browser, FIDO U2F is chosen as a transport layer. At no time, is FIDO2 used.

The core use-case is to provide storage for the private keys/shares of data strings, used in turn to access data in a bigger database (e.g. blockchain), or other secrets.

Initialization Process

Generic device initialization should be similar to the following scenario:

  • User registers to the service using standard login and password credentials, as well as FIDO U2F mechanics for device authentication (standard FIDO U2F register/authenticate calls). Additionally, device’s genuity is confirmed via the latter thanks to the embedded FIDO U2F certificate.

  • Service, through Javascript application, helps user setting up the PIN for the Custom Storage, if it is not set yet (CS_PIN_SET, CS_PIN_ATTEMPTS; user confirms the former with the touch-button press). Optionally activation procedure is run beforehand, if not done yet (CS_ACTIVATION_BEGIN)

  • JavaScript application (JSApp) asks user for PIN, and request to log in to CS (CS_LOGIN). User confirms by pressing the touch-button.

  • JSApp checks for free space and, if available, writes confidential data to the device (CS_FREE, CS_WRITE).

  • In case service needs to make another CS operation, and authorization token will be invalidated, JSApp asks again for PIN to performs login (CS_LOGIN;userconfirmsbypressingthetouch-button), and the target operation.

  • JSApp logs out (CS_LOGOUT).

Device Backup

As the SafeKey device allows only per-origin backup, complete device backup requires repeating the following scenario over each origin.

Export Process

  • User logs in to the service via standard means (login, password and FIDO U2F authentication).

  • User requests exporting data from the device.

  • JavaScript application (JSApp) asks user for PIN, and logs in to CS (user confirms by pressing the touch-button).

  • JSApp requests random data from the device, and generates a word list, which will be used as a passphrase for the backup session (CS_GET_RANDOM). Shows the generated passphrase to user, and asks to print it or save on external encrypted device.

  • JSApp starts backup session (CS_BACKUP_BEGIN; user confirms by pressing the touch-button) by providing the generated passphrase, stores the received SALT, and runs export through all device’s records (CS_BACKUP_READ). Only the records from the same origin will be read properly, otherwise command will fail. There is no implemented command to know, which records are from the current origin.

  • JSApp finishes backup session (CS_BACKUP_FINISH), and stores the data either in cloud, or on user’s PC.

  • JSApp logs out (CS_LOGOUT).

Import Process

  • User logs in to the service via standard means (login, password and FIDO U2F authentication).

  • User requests importing data to the device.

  • JavaScript application (JSApp) asks user for PIN, and logs in to CS.

  • JSApp asks user for backup passphrase, and the backup file to import.

  • JSApp starts backup session (CS_BACKUP_BEGIN; user confirms by pressing the touch-button) by providing the passphrase and SALT, and runs import through all listed records in the archive (CS_BACKUP_WRITE).

  • JSApp finishes backup session (CS_BACKUP_FINISH).

  • JSApp logs out (CS_LOGOUT).

Device Clone

Device complete clone of the CS in one operation is currently not possible. It is realized with running the backup export procedure for each of the origins on device A, and then importing all of them on device B.

Activation Procedure

To use the Custom Storage commands, a device has to be ‘activated’. See the custom command list of commands, and their need for activation.

Activation could be done locally (during the production), and remotely (on the user site). The latter could be realized by the following steps:

  • User logs in to the service via standard means (login, password and FIDO U2F authentication).

  • User requests activation.

  • JavaScript application (JSApp) asks device (CS_ACTIVATION_BEGIN; user confirms by pressing the touch-button) of its serial number, and one-use number, then sends request to the service, and receives the valid signature. The latter is then send to the device (CS_ACTIVATION_FINISH).

More about activation procedure in the activation section.

Device Personalization

Device allows to set/change the PIN (CS_PIN_SET,CS_PIN_CHANGE) with CS and FIDO2 commands.

Through FIDO2 functionality (Resident Keys) it is possible to set the image and name/username of the user.

Clearing the Device

To completely clear the device (CS and FIDO U2F/FIDO2), it suffices to run the factory-reset command (CS_FACTORY_RESET), while being logged in to the CS.

It requires user confirmation by pressing the touch button.

The same effect could be achieved with calling FIDO2 reset command.

Device is Lost, Damaged or Stolen

In case, when device ceased to be available to user (e.g. due to being lost, or stolen), it suffices to import previously backed up data records to another device.

Procedure has to be repeated over all sites (origins), where user has run the backup export procedure.

Backup Mechanisms

Backup procedure is introduced as a mean to:

  • allow to keep the same data on more than one device;

  • allow to export the user data to the cloud.

Backup operation is realized through AES256-CBC encryption of each data record, with a common AES encryption key to the specific export session. The AES encryption key is a result of PBKDF2 calculation against a random salt and user / device-generated passphrase. This key is different from the CS Master AES encryption key, and used only for this specific backup session. Each exported data record gets its own IV, which is sent in clear text. Data slots’ contents are encrypted and exported as-is, as described in the Internal storage layout section.2

Backup is divided to 3 stages:

  • Initialization: backup process initialization(command BACKUP_BEGIN);

  • Actual backup operations (BACKUP_READ or BACKUP_WRITE);

  • Finalization: memory clearing from the used secrets (BACKUP_FINISH).

Command parameters are described in the foreseen Custom Parameters Section

Backup Initialization


BACKUP_BEGIN command accepts passphrase as its parameter, for generating current backup-session AES key. The user should either choose hers passphrase (not longer than 256 bytes; e.g. by using an excerpt from a book), or use the generated one by the device, with the minimal entropy of 128 bit (e.g. 12 words of BIP#39 conforming wordlist). The passphrase derivation algorithm is described in separate $file.

Entropy should be checked by the JavaScript application before sending to the device. Only length of the passphrase is validated (minimum: 16 characters, maximum: 256). After user confirmation (by touch the button) the process will start. Further the passphrase (either users’ or generated) will be processed by the PBKDF2, giving backups’ final AES key:

SALT = hw_random(32)
k_backup = PBKDF2(passphrase, SALT, 100) 


  • hw_random(n) is a device random generator, returning n bytes;

  • SALT is the 256-bit salt for the PBKDF2 function;

  • passphrase is the chosen user passphrase, guaranteed to have at least 128-bit entropy;

  • k_backup is the final backup AES encryption key, used to encrypt the exported data records.

Device will return PBKDF2’s 256-bit SALT (which needs to be provided on import operation). It needs to be stored along with the backups to recover the AES key. Salt guarantees, that even when the exactly same passphrase is used multiple times, the resulted AES key will be different.

Backup Operation

Export / Read


Backup export of each data slot has to be called separately (giving the slot index as the parameter). As a result, besides the encrypted data of the given data slot, IV will be generated and HMAC calculated for the given call. HMAC is calculated using SHA256-AES256-HMAC.

The whole procedure could be presented in pseudo-code as:

read from CS slot
data_plaintext = AES256_CBC_de(aes_master_key, data_slot_encrypted, slot_IV)
actual backup 
IV = hw_random(32) 
data_encrypted = AES256_CBC_en(k_backup, data_plaintext, IV)
HMAC = SHA256-AES256-HMAC(k_backup, {data_encrypted, IV})
exported_slot = {data_encrypted, HMAC, IV} 


  • aes_master_key - is the key used for encrypting the CS data on the given device (see the Internal storage encryption section for details);

  • hw_random(n) is a device random generator, returning n bytes;

  • IV is an initialization vector for given backup record;

  • slot_IV is an initialization vector for given CS slot;

  • AES256_CBC_de(key, ciphertext, IV) - decrypts data using AES256 in CBC mode, with key key, ciphertext ciphertext, and initialization vector IV;

  • AES256_CBC_en(key, plaintext, IV) - encrypts data using AES256 in CBC mode, with key key, plaintext plaintext, and initialization vector IV;

  • SHA256-AES256-HMAC(key, ciphertext)-calculatesHMACvaluefor ciphertext authentication, before running the actual decryption;

  • exported_slot is the final result of the BACKUP_READ command call.

It is not possible to backup data cross-origin. Data record is exported as-is, including its current origin setting.

Import / Write


For each exported data slot backup import command has to be called separately. If the calculated HMAC is not the same, as the received one with the exported data record, the import process is cancelled. In pseudocode:

HMAC_calculated = SHA256-AES256-HMAC(k_backup, {data_encrypted, IV} ) 
if (HMAC_calculated != HMAC_received) then return error 
data_plaintext = AES256_CBC_de(k_backup, data_encrypted, IV)
# write to CS 
slot_encrypted = AES256_CBC_en(aes_master_key, data_plaintext, slot_IV) 


  • HMAC_calculated is a HMAC value calculated during the import from {data_encrypted, IV} pair;

  • HMAC_received is a HMAC value, which was provided with given backup record;

  • data_plaintext is decrypted record data;

  • slot_encrypted is the final data, written to the CS;

For other terms’ descriptions please refer to the previous legend.

Backup writing command does not allow to store record with an ID already existing in the CS. Client application should decide what to do next in case of such event: either skip the import or remove the currently stored record and try again.

Cross-Origin Read Protection

Data entries are written in the sequence they are received by the device, and are written as-is (with the origin being included inside the backup already). In the edge case due to the cross-site access protection, it is possible to not access the exported data, if the origin (e.g. main domain name) has changed between the export and import times, like in the following scenario:


  • User uses service provided by domain AAA.

  • Data are backed up on domain AAA (only data records with this origin).

  • Domain AAA is changing name to BBB.

  • User restores the same backup from domain BBB.

  • User cannot access the restored data with the JavaScript application being placed on BBB, since the origin in the imported data records is AAA.

Subdomain names’ changes are not affected, since according to the FIDO U2F standard, the ingredients for the origin are: hostname, protocol, port. The same limitation is imposed for FIDO U2F authentication mechanism by design.

Backup Finalization and Clearing


When all the operations are finished, final command has to be called to clear the AES key from the device’s memory. In case the device will be powered off before that, the result should be the same. If the device will be continued to be powered though, this should be called to ensure no accidental or malicious imports or exports will further be executed (reads or writes are not confirmed - only at beginning of the backup operation. Another backup import or export procedure cannot be called, until the backup is finalized in the given power cycle.

Backup Mnemonic Passphrase Derivation

This chapter describes the mnemonic passphrase derivation operation, used to generate human-readable secret for the backup procedure.

Mnemonic passphrase derivation is to provide unique secret for each of the backups, taking advantage of the device’s hardware random number generator (to make sure the uniqueness of the secret between backups).

Further BIP#39 word list is used, to translate it to a human readable form (instead of usual group of hard to read random characters passwords). To create the mnemonic passphrase a word list is used, along with the hardware random number generator.

The result of mapping the generated number onto the word list will be 12 words for 128-bit length (minimum to achieve security), up to 24 words for 256-bit length.

Passphrase generation process is almost entirely client side, except for generating the actual number - then the device’s hardware random number generator is used.

Word List

Ideally wordlist should be selected respectfully to the user’s language, unless it is not available - then English or custom one (it should follow the rules of BIP#39 word list).

Words should be easily distinguishable and should avoid similar characters.

BIP#39 word list contains 2048 words, which maps to each 11 bits of secret: log2 2048 = 11 To have a 128-bit long secret, 12 words have to be used: ceil(128/11) = 12 First 10 lines of the BIP#39 word list-english are:


abandon ability able about above absent absorb abstract absurd abuse access

Mnemonic Passphrase Generation

JavaScript application generates the mnemonic passphrase by getting the random numbers sequence from the device, dividing the sequence to 11-bit groups, and mapping it over the word list.

For example, for these bytes the following words will be assigned (using standard BIP#39 English word list):

  • Bytes: 00000010 01100001 00011000 10001110 1xxxxxxx

  • Resulting bit groups: 00000010011 00001000110 00100011101

  • Resulting numbers (decimal integer): 19 70 285

Numbers map to:

  • 19: act

  • 70: angle

  • 285: casual

  • (…)

The resulting mnemonic passphrase should be shown to user, and further used as an input for the backup initialization operation.

Security Design


The solution was designed under the following threat model:

  • Adversary does not have physical access to the device, while it is operating;

  • Adversary does not have physical access multiple times to the device, while it is not operating;

  • Used PC has no malware or viruses installed;

Breaking rule 1 (unlikely) (when the hardware read-protection / debugger deactivation protection has been broken) allows adversary to access the CS encryption key with the specialized equipment.

Breaking rule 2 weakens the encryption security of data, if adversary somehow breaks the hardware read-protection (unlikely), and downloads the encrypted content multiple times (which was changed between the attempts), with the aim of deduce the used AES key.

With breaking the rule 3 (possible) adversary can eavesdrop the USB communication, over which plaintext data are sent to the browser, and later to the server. Introducing encrypted channel device-browser would potentially complicate the attack, however it would be still possible to break it, e.g. with Man-In-The-Middle (MITM) technique. The only possible way to avoid it is probably to reach direct encrypted connection between the device and the server.


  • Custom Storage (CS) contains sensitive data, which cannot be accessed by a 3rd party;

  • CS should remain accessible for a long-term duration (~10 years);

  • It should not be possible to clear the CS easily;

  • Origin domains will not access the data cross-site;

  • Device should provide FIDO2/FIDO U2F features;

  • It should be possible to activate custom features via an additional ECC key (see Activation chapter);

  • Device should use only signed firmware, and block older firmware than current;

  • Device should not lose data between the firmware updates;

Point4 holds both for regular data access, as well as for backups-these are collecting data per origin only.

Potential Hazards

It was proven on another MCU model, and with a lower security standard, that despite having read-only hardware protections in place, it was possible to change the memory address containing the RDP flag, which is in charge of the internal memory read-protection.

While it is not applicable for this model (due to disabled debugger interface), it always should be kept in mind that device’s firmware could be modified by an adversary despite having hardware, and firmware protections applied.

DOS Protection

Briefly, to make a Denial Of Service attack on the device, adversary has to use up all of the PIN attempts, so the user could not use it anymore.

The device offers 8 attempts total, and allows only 3 attempts maximum per power cycle, so the malware will not use all attempts at once, and user will notice unexpected behaviour.

Internal Storage Encryption

Storage encryption denies access to the sensitive Custom Storage (CS) user data via external means, while device is at rest.

Encryption essentially is realized through 256-bit AES-CBCESSIV, and the master key is wrapped by AES256-CBC with key based on PBKDF2 hash, derived from user PIN.

Data Encryption and Decryption

Used encryption scheme is a variation of 256-bitAES-CBC-ESSIV, with data slot index-derived IV. It works by encrypting each slot data with the same AES key, but with IV quasi-randomized per slot, for given AES key instance.

This type of encryption was used in the past by Microsoft in its BitLocker solution. Alternative algorithm is AES-XTS, however in this case it seems to bring higher complexity with none or small advantages.

The 256-bit AES key for internal storage is generated in the very first use of the Custom Storage. In the implemented solution, at rest, full confidentiality is guaranteed.

There is no authentication / protection from tampering with data, but under assumption adversary will not have physical access to the device (to modify its internals), this is not required.


Infiltrate the Vault: Security Analysis and Decryption of Lion Full Disk Encryption: https://eprint.iacr.org/2012/374.pdf

Public Comments on the XTS-AES Mode: https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/comments/xts/collected_xts_comments.pdf

Data Access

Data from CS is accessed by each data slot separately. Each slot is decrypted only when required. Only one slot is decrypted at a time. The AES key is common; however IV AES parameter is different for each slot, and is parametrized by the currently handled data slot index.

Following pseudocode describes, how decryption is implemented:

IV_record = SHA256( {IV_SALT, data_record_index} )
data_record_plaintext = decrypt(master_aes_key,
IV_record, data_record_encrypted)

Encryption is done similarly:

IV_record = SHA256( {IV_SALT, data_record_index} )
data_record_encrypted = encrypt(master_aes_key,
IV_record, data_plaintext)


  • IV_SALT is salt used to generate the IV, kept along with AES key, and treated the same way as the master AES key (described below);

  • master_aes_key is the 256-bit key used to encrypt/decrypt user data (description below in key details chapter)

  • encrypt(master_aes_key, IV, plaintext) is an AES 256 encryption function, taking AES key master_aes_key, IV initialization vector, and plaintext data plaintext;

  • decrypt(master_aes_key, IV, ciphertext) is an AES 256 decryption function, taking AES key master_aes_key, IV initialization vector, and encrypted data ciphertext;

  • {IV_SALT, data_record_index} is a concatenated binary array of data IV_SALT and data_record_index.

Master Encryption Key Details

Master encryption key is generated on the device’s initialization, and stored in the encrypted form with another AES key, based on the user’s FIDO2 PIN.

It is unlocked only after providing the correct PIN, and cannot be restored without it. It never leaves the device by design.

Key Generation

Master encryption AES key is generated during the very first use of the Custom Storage (CS), never stored raw to the embedded flash, and will never change until factory reset is requested.

It is created using the hardware number generator. Further, it is encrypted using AES256 by another AES key, derived from the user PIN (via PBKDF2) provided to unlock the CS.

The encrypted form of the AES key is stored in the MCU’s flash memory. Key is re-encrypted in case of the FIDO2 PIN change.

cPBKDF2 iterations count is set to 100, as a compromise between usability and security (it takes 200ms for the device to calculate it), hence the longer SALT.

To present master AES key initialization in pseudo-code:

SALT = random(32)
master_aes_key = random(32)
IV_SALT = random(32)
k_stored = encrypt( {master_aes_key,IV_SALT}, 0, k_PBKDF2)

Key Usage

Key is loaded to memory after successful activation of the CS LOGIN command. It is recreated in the same way, as it was generated. That is the key is built as:

master_aes_key, IV_SALT = decrypt(k_stored, 0, k_PBKDF2)

It is removed from the RAM memory the moment the authentication session is cleared, which is either after the requested clear / logout, or after 60 seconds.

Key Erase / Storage Clearing

The moment the CS’s AES key is removed from the internal storage memory, the CS will not be possible to read again, even when the same PIN is provided.

This allows to securely clear the device, and the CS specifically.

Key Storage

Key in the encrypted form is stored in the main STATE structure.

For the flash memory details please look into the flash layout.

PIN handling

PIN (short from Personal Identification Number) is similar to password in a way but differs in complexity and use case. PIN is a short string of characters, easy to remember for user.

On the contrary to the password, PIN cannot be ‘brute-forced’ (that is, all combinations of characters cannot be checked) due to limited attempts user would be asked for it.

For FIDO2, device will ask for PIN 8 times, after which it will go to a ‘blocked’ state, disallowing further use of the device until it will be reinitialized (all user data, including attempts counter and current PIN, will be cleared).

For FIDO2 PIN, minimal length defined in the FIDO2 standard, is 4 bytes, and maximum is 63. This means it can hold 63 ASCII characters, or 15 (63/4) 4-byte wide Unicode characters (e.g. UTF-32).

FIDO2 standard defines the PIN to be UTF-8 encoded, however device accepts any form, and is encoding agnostic (it compares binary data).

FIDO2 PIN is required for FIDO2 registration and authentication actions, as well as using the Resident Keys feature.

Since FIDO2 PIN is used for the Custom Storage (CS) access (it is possible to decouple it), it will be required as well for each login action to the CS, to use its features.

Despite using FIDO2 PIN, all transport is still conducted through FIDO U2F layer, where the custom commands are sent.

Simply FIDO2 PIN is sent through FIDO U2F using the custom command. At no time FIDO2 is used to call CS commands.

Same PIN is used for usability - to not add another PIN for user to remember, to use the device full capabilities.

PIN Attempts Counter

With PIN an attempts counter is associated, which will decrease with each invalid PIN provided (that is other, than currently set), and resets otherwise.

Only 3 attempts are possible in the given power cycle, and 8 attempts total

This protects e.g. from a malware trying to block the device. (DOS protection)

Device’s Blocked State

If all the 8 attempts are used up for entering the valid PIN, the device will enter the blocked state, where all FIDO2 and CS functionality will be blocked.

It will remain in this state, until FIDO2 reset command will be issued. With the execution of it, all user data: FIDO U2F, FIDO2 and CS; will be cleared, including the PIN.

PIN setting command is required to be called to use device’s features again, including the CS commands.

PIN Calculations and Storage

FIDO2 PIN is stored in SHA-256 hashed version in the device’s general configuration.

The hash is salted against a 256-bit random number, generated during the very first initialization of the device, and only the first 128-bits of it are stored and used for the validation.

This forbids adversary to learn the true PIN’s cleartext user had initially set up.

To show calculations in the pseudo-code for the CS PIN validation:

interm_hash = SHA256(incoming_PIN)
incoming_PIN_hash = SHA256({interm_hash[:16], PIN_SALT})
PIN_correct = (incoming_PIN_hash == stored_PIN_hash)


  • incoming_PIN is the PIN device is testing to be valid;

  • PIN_SALT is 256-bit salt number for the SHA256 hash;

  • stored_PIN_hash is the currently set on the device user PIN SHA256 hash, with size of 16 bytes;

  • interm_hash is an intermediate 256-bit SHA256 hash;

  • interm_hash[:16] is the first 16 bytes of interm_hash;

  • incoming_PIN_hash is the final 256-bit hash of incoming PIN, which is tested against stored value;

  • PIN_correct is the boolean final result of the byte-to-byte comparison of both hashes.

The reason that hash is calculated two times is because of the FIDO2 PIN handling - the user provided PIN there is never transported to the device in plaintext, but instead only first 16 bytes of its SHA256 hash.

Thus, to make use of the same PIN as FIDO2, client-side hashing has to be simulated on the device for CS PIN validation.


PIN is used every time before access to the user’s secret data has to be confirmed: for FIDO2 and Custom Storage. It is shared among these for usability reasons - to minimalize the required PINs count to use the device’s full capability

Custom Storage

PIN is required for all the commands related to access or write of the Custom Storage data, since it contains information needed to decrypt the Master AES key.

PIN is provided to the device via the LOGIN command, to make an authenticated session.

It is possible to change the PIN via the PIN_CHANGE command.


PIN is requested for FIDO2 registration and authentication operations.

It is possible to change the PIN via the standard FIDO2 procedures.


PIN is unused for FIDO U2F operations.

PIN Authentication

To manage Custom Storage (CS) space, commands need to be authenticated.

To achieve that, client application has to login to CS (CS_LOGIN command) with current user PIN.

Once the device confirms the PIN, it generates a random value, which will be treated as a temporary authentication token, valid for 1 minute.

Token will be cleared from memory either after its timeouts, the log out command will be called (CS_LOGOUT), or it will be replaced with another token with further login command calls (CS_LOGIN).

Features Activation

Activation feature allows to enable or disable Custom Storage (CS) commands on the given custom FIDO2 device. It is based on the Elliptic Curve Cryptography (ECC), with set of operations based on public and private keys.

By default, CS commands are disabled, and device returns error code on attempt of their execution.


Activation could be performed locally, with a potential extension to remote work (without firmware change). No one besides the person in the possession of the key can perform the procedure. The received activation signature is restricted to the given device, and valid for only a single request, due to the use of MCU’s serial number, and a randomly device-generated transaction token. Data received from the device allow to identify it, and track the activation status on the server.

Attributes of the activating response:

  • cannot be used on multiple devices

  • cannot be used again on the same device

The clue of the solution is taking advantage of the ECC signature, done over a hashed data received from the device: expected activation state, serial number and a nonce (randomly generated on the device, one-use number). This signature, calculated by the activation tool (either locally, or remotely by a distant server), is then validated on the device - after confirmation the custom features are available for use by the JavaScript application.

To illustrate in pseudo-code:

hash = sha256(SN, nonce, state)
signature = ECC_sign(key_priv, hash)


  • key_priv - private part of the ECC key

  • SN - the serial number of the MCU;

  • nonce - a device-sourced random number, which would protect from the replay attack;

  • state - target working mode of the device (custom features activated/deactivated).

Unlock Passphrase (PUK Equivalent)

Unlock passphrase is meant to get access to the Custom Storage (CS) after it being blocked by multiple invalid PIN attempts through either CS or FIDO2. This as well protects the CS data from being accidentally removed by the FIDO2 reset operation (if generated beforehand).

It can be thought as an equivalent of a PUK code known from the mobile’s SIM card.

To unlock the device, user has to call Unlock command, with a valid unlock passphrase, and a new PIN to be set for FIDO2/CS access.

Unlock passphrase consists of 12 words, mapped internally according to the BIP#39 to 128-bit secret.

Both passphrase generation and unlocking commands have to be confirmed by pressing the touch button before execution. Unlock passphrase does not have an attempt counter and can be tried indefinitely.

However, given the vast search space (2^128 possibilities) the brute-force attack is not feasible, hence the counter is not needed.

Operation Modes

Device offers two operations modes, depending on the unlock passphrase being generated or not.

Normal Mode

When unlock passphrase is not generated - CS data will be removed on the FIDO2 reset. Access to the CS will never be blocked permanently.

Protected Mode

When unlock passphrase is generated - CS data will not be removed on the FIDO2 reset event.

The device can be still used as a proper FIDO2 authenticator with any PIN. CS and its data can be accessed any time with the Unlock command provided, that user knows the unlock passphrase.

Otherwise the CS access will remain blocked indefinitely, and the data are lost.

Technical Details

Generating Passphrase

Passphrase can be generated using the Unlock-Generate (CS_UNLOCK_GENERATE) command. Once generated, the passphrase cannot be removed or deactivated. It can be re-generated though, while logged in to the CS.

It can be viewed only once - in case it was lost by the user, it should be re-generated, and the new passphrase should be stored. The passphrase is generated from the on-device hardware random number generator (HWRNG).

The internal AES master key is encrypted with it and stored alongside the usual form encrypted by the PIN. The HMAC is calculated to make sure of the data correctness during the unlocking.

To present in pseudo-code:

unlock_passphrase = HWRNG(16) 
unlock_master_key_encrypted = AES256_CBC_encrypt(unlock_passphrase, AES_master_key, IV=0) 
unlock_master_key_HMAC = HMAC(unlock_passphrase, unlock_master_key_encrypted) 
CS_STATE.unlock_passphrase.is_set = true

Unlocking CS Access

Access to the CS can be unlocked with the Unlock command (CS_UNLOCK). The arguments are the unlock passphrase, and the new PIN, which will be shared with the FIDO2 application.

Unlock passphrase is used to calculate the HMAC for validation, and then the AES_master_key is decrypted.

Further it is encrypted with the new PIN to allow regular CS access, and the PIN is set to current for the FIDO2 application as well.

To describe in pseudo-code:

incoming_HMAC = HMAC(user_provided_unlock_passphrase, unlock_master_key_encrypted) 
assert unlock_master_key_HMAC == incoming_HMAC 
AES_master_key = AES256_CBC_decrypt(unlock_passphrase, unlock_master_key_encrypted, IV=0) 
PIN = new_PIN


Bootloader is the first application code executed after device is powered up. It checks for the validity flag, and after that runs the uploaded actual main application. Otherwise it stays in this mode, until the new main application is uploaded and confirmed valid.

Its purpose is to allow changing the firmware of the device, and to guard against malicious attempts of doing so.

Bootloader cannot be updated in the device’s lifecycle.

It cannot write outside the specified application zone.

Firmware Signature

Firmware signature is an ECC (secp256r1) signature over SHA256 sum of the whole main application:

valid = ECC_verify(key_bootloader, sha256(uploaded_application))

The signature could be produced only by the private key owner of given ECC key pair and is attached to the firmware binary. It allows to run only the firmware officially signed.

In this version two keys are uploaded, to allow updates from both NitroKey, and SafeTech.

Public parts of the key pairs are included directly in the firmware, are not secrets, and are not update-able during the device’s lifecycle.

Firmware Update Procedure

The firmware update algorithm is as follows:

  1. User initiates the firmware update by sending the proper command while device is working mode, and confirms by the touch-button touch;

  2. Device switches to the bootloader mode, and waits for further instructions;

  3. Host PC sends firmware in chunks, with a WRITE command. First WRITE clears the whole application memory, and the flag allowing to execute the application code. User data stays in place as-is;

  4. After sending whole firmware, host PC sends firmware signature and a validation request, on which device computes the SHA256 hash over the writable application space, and compares with the value of the signature;

  5. On an invalid signature bootloader returns error. On the next power cycle device will boot to the bootloader mode, despite having application code uploaded. Update procedure will stop here;

  6. On a valid signature device will check the uploaded firmware version. If it is newer, or the same, as last used, the boot flag will be changed to allow the firmware to run. Neither firmware code, nor its version, will be verified on the next power cycles - only the flag state;

  7. Device will reboot to the application mode.

In case the firmware update will be stop in the middle (e.g. due to power outage, host PC crash etc.), it suffices to run it once again.

The update procedure is completely safe, and it is not possible to break the device with it. Boot validation flag is set only after doing signature and firmware version check - before that the uploaded code is not used in any way by the device.

The only case firmware update would render the device unusable, is when the faulty firmware is released, which does not allow to boot to bootloader to execute further updates.

Downgrade Protection

Downgrade protection purpose is to disallow adversary of malicious upload of the older firmware, which has known critical issues, to avoid breaching device protections.

It guarantees that once the user has updated the device, it will not be downgraded, and old known issues will not be exploited.

Version validation is executed as in:

allow_to_run =
uploaded_firmware_version >= last_used_firmware_version

In other words, the following scenario is implemented:

  1. Device has firmware version x;

  2. User tries to update firmware to version x+1 and succeeds;

  3. User tries to change firmware back to version x, or any lower than x+1, but fails due to downgrade protection;

  4. User tries to upload firmware version x+1 and succeeds.

Uploaded firmware validation is executed only after the main application is overwritten; thus it is allowed to write the latest firmware version again.

Memory Layout

The version data is stored in the last page before the application page, which at the same time is the last bootloader page.

Update Over Browser

Bootloader allows to update over FIDO U2F protocol, by using FIDO U2F Authentication command with specific data header in the buffer, which is further checked internally by the device.

Origin Protection

There is no origin protection for the firmware update. It is not needed, since the firmware is validated on the device against a public key embedded inside the bootloader code.

Due to the validation it is not possible to write modified, or custom code, onto the device; thus, the distribution and update over multiple potentially untrusted sites is secure, as long as keys for signing the firmware are secure.

Device’s Operational Conditions


According to the official specification, used MCU - STM32L432 - can operate in the following environment (specification, page 12, chapter 2 Description, STM32L432Kx family device features and peripheral counts):

MCU’s Internal Flash Reliability Notes

The following table describes retention parameters of the STM32L432 internal flash memory, which are guaranteed by characterization results (specification, page 103, 6.3.10 Electrical characteristics / Flash memory characteristics, table 51. Flash memory endurance and data retention):

where T_A is ambient temperature.

ElectroStatic Discharge (ESD)

Producer claims MCU is resistant to voltage up to 2 kV (human body model), and 250 V (charge device model).

Besides this, the SafeKey FIDO2 platform has additional ESD protection added to the design.

Source: specification, chapter 6.3.12, Electrical sensitivity characteristics, page 105, table 54. ESD absolute maximum ratings.

For more detailed information about the MCU used in our SafeKey, please take a look at the MCU Datasheet https://www.st.com/resource/en/datasheet/stm32l432kc.pdf

Last updated