One of the key benefits of the nRF Connect SDK is that it contains many drivers, libraries, and subsystems offering a comprehensive set of features, allowing developers to focus on their application and accelerate their time to market. However, there might also be multiple solutions for similar use cases, making it difficult to know which one you should use in your project.
One such use case is how to store keys and data persistently. This blog post aims to cover options for storing keys and data so that you can make an informed choice on selecting the proper mechanism that fulfills your application’s requirements.
Table of Contents
Short summary
For you who are here to see which storage methods are recommended for storing data or keys, but want to avoid reading the whole post, here is the TL;DR:
-
If you want to work with keys for cryptographic operations, we suggest you start with the PSA Crypto API.
-
If you use TF-M and want to store sensitive data, we suggest the PSA Protected Storage API.
-
If you want to write and read data from non-volatile memory, we suggest starting with the Non-Volatile Storage (NVS) subsystem.
See the PSA Protected Storage section for a comparison of “when to use PSA Protected Storage vs NVS.”
Storage alternatives for data
The table below contains an overview of all the available storage alternatives and the features supported by each of them. The features and storage alternatives are discussed in more detail in the subsections.
Feature/ |
Partitioning |
Integrity |
Isolation |
Encryption |
---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* The Settings subsystem can use different backends. If the backend has data integrity, Settings also does.
Features
The horizontal axis of the above lookup tables lists a set of features that persistent storage can have. In the following subsections, we explain the features, but it is up to you to decide which ones your application needs. Features come with a cost, for example, storage overhead and/or complexity and code size, so "the more features, the better" does not necessarily apply.
Partitioning
One of the challenges when using microcontrollers is not to overwrite non-volatile memory by accident.
The nRF Connect SDK uses either DeviceTree fixed flash partitions or the Partition Manager to partition non-volatile memory. Storage subsystems will get ownership over different non-volatile memory partitions, preventing subsystems from overwriting each other's data. The DeviceTree fixed non-volatile memory partitions are the Zephyr system for partitioning. If the Partition Manager is not enabled, this method is used.
The Partition Manager is an nRF Connect SDK-specific tool that dynamically or statically allocates partitions at build. The Partition Manager is automatically enabled for Multi-Image Builds (for example, when using a Bootloader or dual-core builds).
Integrity
Data integrity is a broad topic, but in this blog, we define it as "making sure the data you read is the data you wrote.” It is usually handled by using a Cyclic Redundancy Check (CRC) or a cryptographic hash function (like SHA-2). This blog post will not detail how each subsystem handles integrity. To learn more, please check the documentation for the specific subsystems.
Isolation
Trusted Firmware-M (TF-M) implements isolation between the Non-Secure Processing Environment (NSPE) and Secure Processing Environment (SPE) as a security mechanism. Therefore, this is part of the Arm Platform Security Architecture (PSA). For more information on isolation, NSPE, and SPE, see our previous blog: An Introduction to Trusted Firmware-M (TF-M).
Encryption
Some storage subsystem supports encryption of the stored data for additional data-at-rest protection. For details on how this encryption is done, please take a look at the documentation for the specific storage subsystems. Generally, the data is encrypted with one of the key types described in this blog post.
Storage alternatives
Let’s discuss more details about the storage alternatives for data in the nRF Connect SDK. This list is not exhaustive; It lists the storage alternatives we believe are most important to learn about. They were chosen either because they are important building blocks for storage (NVMC), they are frequently used by other drivers in the nRF Connect SDK (NVS, Settings), or because the storage alternatives have features that can provide additional value for the application (File Systems, PSA Protected Storage).
NVMC (non-volatile memory controller)
NVMC |
Partitioning
|
Integrity
|
Isolation
|
Encryption
|
---|
The most important part is that the rest of the nRF Connect SDK will use Partitioning for non-volatile memory usage. Since the NVMC allows you to write to any part of non-volatile memory, it can easily overwrite the non-volatile memory used by other subsystems. To avoid this, we suggest you create a custom partition for non-volatile memory usage and keep it inside this region for NVMC writes.
We do not recommend this method for persistent storage, as it places a lot of responsibility on the developer. If you just want to store some data persistently, Non-Volatile Storage (NVS) is a more suitable option, discussed in the following subsection.
Since this is not a recommended storage method, we do not have any samples in the SDK showcasing how to use it. However, all other persistent storage subsystems are built on top of the NVMC, making it a fundamental building block in the SDK.
NVS (non-volatile storage)
NVS |
Partitioning
|
Integrity
|
Isolation
|
Encryption
|
---|
Zephyr's default persistent storage subsystem is the Non-Volatile Storage (NVS) subsystem. From the documentation, NVS can be described as: “Elements, represented as id-data pairs, are stored in non-volatile memory using a FIFO-managed circular buffer.”. NVS uses the NVMC driver to store data in non-volatile memory.
One of the most practical aspects of the NVS subsystem is that it doesn’t have many functions and is easy to understand. The basic functionality of NVS is provided with three function calls: nvs_mount(), nvs_write(), and nvs_read().
Behind the API, NVS automatically tracks the metadata for stored elements: id, data offset in the sector, data length, part (unused), and CRC. The CRC part of the metadata is used to check the data's integrity automatically. During initialization, NVS will verify the data stored in non-volatile memory. If it encounters an error, it will ignore any data with missing/incorrect metadata.
An NVS sample is available to get you started, and we recommend reading Non-Volatile Storage (NVS) for additional information on this subsystem.
File Systems
File Systems |
Partitioning
|
Integrity
|
Isolation
|
Encryption
|
---|
Zephyr has a File System API for interfacing file systems. Two file systems are available by default in Zephyr: Fat Filesystem and Littlefs. Both these have samples, which can be found under FS Samples.
File systems are useful for storing data as files. They also provide widely used formats, making sharing data with other devices more accessible. For example, the FAT Filesystem is supported on Windows, Linux, and MacOS, so you can use this filesystem and a USB driver to connect your device to a PC as a storage drive.
Settings
Settings |
Partitioning
|
Integrity * |
Isolation
|
Encryption
|
---|
Settings define a structure and method for organizing and storing settings and configuration data. This subsystem does not write to non-volatile memory directly itself. Instead, it uses other subsystems, which are selected in the configuration. NVS is recommended for this.
To learn how the Settings structure works, please look at Settings documentation. It can be summarized by “The Settings subsystem gives modules a way to store persistent per-device configuration and runtime state.”. In other words, you can define sets of variables from your application and save these with the Settings subsystem. After a reboot, you can call settings_load() to load the values back to all these variables. Settings are used by various other subsystems, such as the Bluetooth host stack and Matter, to save configurations persistently.
If the Settings subsystem uses NVS to store its data, it gains benefits of NVS, such as CRC checks for integrity. There is also a Settings Sample available.
* The Settings subsystem can use different backends. If the backend has integrity, Settings also does.
PSA Protected Storage
PSA Protected |
Partitioning
|
Integrity
|
Isolation
|
Encryption
|
---|
This section assumes some prior knowledge about Arm Platform Security Architecture (PSA) and Trusted Firmware-M (TF-M). To learn more, please take a look at our previous blog posts, An Introduction to Trusted Firmware-M (TF-M) and Securing IoT products with PSA Certified APIs, where PSA Protected Storage has also been described. We recommended reading those for the PSA Protected Storage details. In this blog, we will focus on how this is different from the other storage alternatives. Official Arm documentation can be found under the PSA Secure Storage documentation.
The PSA Protected Storage API provides functionality for writing and reading data from non-volatile memory, with functions such as psa_ps_set() and psa_ps_get(). Each data element has an associated ID to keep track of the data.
PSA Protected Storage is an Application RoT Service and lives in the SPE. Data stored using PSA Protected Storage can be encrypted and is stored in Secure non-volatile memory with integrity checks.
The PSA Protected Storage is made available to both the NSPE and the SPE. You can use the PSA Protected Storage from your application or your eventual custom Application RoT services. The NSPE can not access data stored using PSA Protected Storage from the SPE, and vice versa.
So if the NVS driver is the default non-volatile storage driver in the nRF Connect SDK, when should you use the PSA Protected Storage API? First and foremost, if you need persistent storage in a custom Application RoT Service, we recommend using PSA Protected Storage. The question becomes more nuanced for persistent storage in the NSPE, as encrypted data is still available to the NSPE using the PSA Protected Storage API. So for this, you will have to choose the storage subsystem yourself. Some reasons to use the PSA Protected Storage are as follows:
-
You need the data to be encrypted at rest.
-
You need to store the data in a way that is PSA Certified compliant.
-
PSA Protected Storage supports a write_once flag for data.
For an example of using the PSA Protected Storage API from the NSPE, please look at the PSA Protected Storage sample. If you want to use the PSA Protected Storage API from the SPE, combine the PSA Protected Storage sample and our TF-M secure peripheral partition sample.
PSA Internal Trusted Storage
PSA Internal Trusted Storage is often mentioned in the same sentences as PSA Protected Storage. However, PSA Internal Trusted Storage is only used internally by Trusted Firmware-M. We recommend not using the PSA Internal Trusted Storage API directly. If you need to store persistent generic data from your Application RoT Services, use the PSA Protected Storage API instead. If you need to do key management from your Application RoT Services, use the PSA Crypto API, which is described further down in this blog.
Storage alternatives for keys
Keys are usually used by cryptographic operations and are similar to data in a way. While not recommended, you can save keys using any of the above data storage methods and work manually with your keys.
However, the nRF Connect SDK has different features for storing and/or working with keys. In the following subsections, we will cover all the key storage features available and supported in the nRF Connect SDK.
PSA Crypto API
This section assumes some prior knowledge about Arm Platform Security Architecture (PSA) and Trusted Firmware-M (TF-M). To learn more, please take a look at our previous blog posts, An Introduction to Trusted Firmware-M (TF-M) and Securing IoT products with PSA Certified APIs, where PSA Crypto API has also been described.
If you need keys for cryptographic operations, I recommend starting with the PSA Crypto API.
The PSA Crypto API has a set of functions for you to work with keys. You can read about these at PSA Crypto API: Key management reference. These functions are designed to be secure and easy to use, so users do not have to worry about secure key handling. However, you must still tell the PSA Crypto API which keys it shall use. There are two alternatives for this: to generate or import keys.
The PSA Crypto API can generate random keys with psa_generate_key(). Generated keys can either be volatile or persistent. Volatile keys reside in SPE RAM. Persistent generated keys are saved in PSA Internal Trusted Storage. An advantage of using the SPE for key generation and storage is that the keys are never available from the NSPE, keeping them protected in case of vulnerabilities in the application.
The PSA Crypto API can also import keys using psa_import_key(). Once the key has been imported, the intent is to no longer provide direct access to the key material but to allow usage of it, given different usage rules. After a key is imported, the NSPE only needs a key reference to make use of this key. Note that while the key is being imported from the NSPE, it temporarily resides in an untrusted environment. Generally, it is safer to generate new keys without exposing them to the NSPE, if possible. Feel free to create a ticket at DevZone if you have questions about key management in specific use cases, and we will try to help.
Samples showcasing usage of the PSA Crypto API can be found in Cryptography samples, and specifically for key storage, the Crypto: Persistent key storage.
HUK
Hardware unique keys (HUKs) are device-specific keys that you can use with functions for key derivation to generate other keys.
This driver supports three different types of keys:
-
Device root key (KDR):
-
Used for deriving general-purpose keys.
-
Stored in non-volatile memory locked by the ACL. It should be written to CryptoCell by the bootloader. See HUK docs for more info.
-
-
Master key encryption key (MKEK):
-
Used for deriving Key Encryption Keys (KEKs), which are used to encrypt other keys when these are stored.
-
Stored in the KMU. Please look at the subsection below for an explanation of the KMU.
-
-
Master key for encrypting external storage (MEXT):
-
Used to derive keys for encrypting data in external storage
-
Stored in the KMU.
-
Different devices support different types of HUKs. See Hardware unique keys (HUKs) for supported devices. For example, the Hardware unique key sample uses the MKEK for the nRF5340 but the KDR for the nRF52840.
HUK covers use cases for keys for drivers in the nRF Connect SDK. You can utilize these keys for your applications as well. Do not use these keys directly, but rather derived keys. The application can provide a label to CryptoCell and receive a new key derived from a HUK. The application can get the same derived key multiple times, as each key derived from the same label+HUK combination will be consistent. Keys can be derived from each HUK, and each HUK can have multiple different labels and, therefore, multiple different derived keys. Please look at the Hardware unique key sample for how to derive keys.
If TF-M is enabled and needs the HUK, the user must ensure the HUK is generated. Please look at the TF-M Provisioning image sample for an example of how this can be done.
Initial Attestation Key / Identity Key
The Initial Attestation Key (IAK), also known as Identity Key, is a key required by TF-M for PSA Attestation. If you want more information on PSA Attestation, you can see Securing IoT products with PSA Certified APIs.
If TF-M requires an IAK, it is up to the user to generate the IAK before running the application with TF-M. Remember that since the IAK is encrypted using MKEK, the MKEK must be generated first. See the TF-M Provisioning image sample for an example of how this can be done. See also the Identity key generation and Identity key usage samples.
KMU
The Key Management Unit (KMU) is a hardware peripheral for the secure storage of keys. Documentation for the KMU is in the product specification for each nRF SoC with support for the KMU, for example, nRF5340: KMU or nRF9160: KMU.
The main benefit of the KMU is that Cryptocell can use KMU keys directly without the CPU knowing the keys. The CPU knows metadata about the key, such as slot number.
The KMU stores keys such as HUK keys and the Initial Attestation Key.
If users have custom keys to store in the KMU, they can use the CC3XX Platform - KMU APIs. Using the KMU directly requires users to handle keys securely. Therefore, consider the PSA Crypto API (potentially alongside the HUK) before choosing to work directly with the KMU yourself. Often these do what you need.
OTP (One Time Programmable)
Some chips have true One Time Programmable (OTP) non-volatile memory. This is a non-volatile memory that can only be programmed once.
The OTP memory is a part of the User Information Configuration Registers (UICR). The UICR is a collection of non-volatile memory registers aside from the general non-volatile memory storage of the chip. The Non-Volatile Memory Controller (NVMC) driver is recommended for using the emulated OTP, specifically nrfx_nvmc_otp_halfword_read.
The OTP is not protected by reading, so anything can read it. However, it is useful for values that should be written once and never changed in the product lifetime, such as User ID or non-reversible activation of features.
Not all nRF devices have OTP peripherals. See Product Specifications for each device, such as nRF5340 Product Specification on OTP or nRF9160 Product Specification on OTP.
Hardware flash write protection
Different nRF chips have different peripherals to limit the applications' access to parts of the flash. These can restrict write and/or read access to chosen areas. In the nRF Connect SDK, the fprotect driver is recommended for locking flash for write and/or read access. The fprotect driver uses a hardware peripheral (BPROT, ACL, or SPU, depending on the nRF SoC) to protect a flash area. Please look at the Product Specification on specific chips for docs on the specific peripherals. For example, nRF52805 on BPROT, nRF52840 on ACL or nRF5340 on SPU.
Modem Certificate Storage
The modem of the nRF91 Series has its own certificate storage and TLS/DTLS driver.
The application can write (provision) certificates to slots in the modem and choose which slots the modem should use when using TLS in communication.
The application can not read out the certificate after it has been written. Therefore, the certificate is often provisioned to the modem before the "actual application" runs. This is similar to the security benefits of the KMU.
For the use of TLS/DTLS with the nRF9160, I recommend the modem TLS/DTLS functionality.
To partition certificates to the nRF9160, see the relevant documentation in Working with nRF91 Series. Also, see the DevAcademy: Cellular IoT Fundamentals course.
Wi-Fi credentials
For storing Wi-Fi Credentials, we provide the Wi-Fi credentials library, built on the PSA Protected Storage API, which makes it easier to safely store Wi-Fi credentials.
APPROTECT
Developers must debug their applications during the development phase, which typically requires a fully accessible debug port. However, once the product has been shipped, you want to keep everyone from accessing registers or non-volatile memory from the device.
APPROTECT will protect against gaining access to the SoC resources, which may include sensitive information. To reprogram the device with APPROTECT enabled, the debugger first needs to erase the non-volatile memory using the Control Access Port (CTRL-AP) interface before being able to regain access, which ensures that any sensitive data gets wiped out before debug port access is restored.
This feature can be used in addition to other measures to give extra security and robustness to your device, which we strongly recommend doing. Please take a look at Enabling access port protection mechanism for more information.
Closing
Persistent storage of data and keys can be done in different ways, and it’s important to pick the mechanism that best suits the needs of your application. Not all the data needs to be stored in the most secure way possible (e.g. battery level), so it’s important to know when to use the various storage alternatives to avoid unnecessary overhead that may negatively impact your code size, performance, or energy consumption.
We hope this blog has helped you better decide which data and key storage mechanisms to use. If you have already used some of these features, leave us a comment below about your experiences and interesting things you might have learned along the way that we might have missed in this blog post.
If you have more detailed technical questions, please create a ticket at DevZone, where we can provide additional support.
Top Comments