Hardware-Wrapped Keys

Like most disk and file encryption software, Android's storage encryption traditionally relies on the raw encryption keys being present in system memory so that the encryption can be performed. Even when the encryption is performed by dedicated hardware rather than by software, software generally still needs to manage the raw encryption keys.

This traditionally isn't seen as a problem because the keys won't be present during an offline attack, which is the main type of attack that storage encryption is intended to protect against. However, there is a desire to provide increased protection against other types of attacks, such as cold boot attacks, and online attacks where an attacker might be able to leak system memory without fully compromising the device.

To solve this problem, Android 11 introduced support for hardware-wrapped keys, where hardware support is present. Hardware-wrapped keys are storage keys that are only known in raw form to dedicated hardware; software only sees and works with these keys in wrapped (encrypted) form. This hardware must be capable of generating and importing storage keys, wrapping storage keys in ephemeral and long-term forms, deriving subkeys, directly programming one subkey into an inline crypto engine, and returning a separate subkey to software.

Note: An inline crypto engine (or inline encryption hardware) refers to hardware that encrypts/decrypts data while it is on its way to/from the storage device. Usually this is a UFS or eMMC host controller that implements the crypto extensions defined by the corresponding JEDEC specification.

Design

This section presents the design of the hardware-wrapped keys feature, including what hardware support is required for it. This discussion focuses on file-based encryption (FBE), but the solution applies to metadata encryption too.

One way to avoid needing the raw encryption keys in system memory would be to keep them only in the keyslots of an inline crypto engine. However, this approach runs into some problems:

  • The number of encryption keys may exceed the number of keyslots.
  • Inline crypto engines can only be used to encrypt/decrypt full blocks of data on-disk. However, in the case of FBE, software still needs to be able to do other cryptographic work such as filenames encryption and deriving key identifiers. Software would still need access to the raw FBE keys in order to do this other work.

To avoid these problems, the storage keys are instead made into hardware-wrapped keys, which can only be unwrapped and used by dedicated hardware. This allows an unlimited number of keys to be supported. In addition, the key hierarchy is modified and partially moved to this hardware, which allows a subkey to be returned to software for tasks which cannot use an inline crypto engine.

Key hierarchy

Keys can be derived from other keys using a KDF (key derivation function) such as HKDF, resulting in a key hierarchy.

The following diagram depicts a typical key hierarchy for FBE when hardware-wrapped keys are not used:

FBE key hierarchy (standard)
Figure 1. FBE key hierarchy (standard)

The FBE class key is the raw encryption key which Android passes to the Linux kernel to unlock a particular set of encrypted directories, such as the credential-encrypted storage for a particular Android user. (In the kernel, this key is called an fscrypt master key.) From this key, the kernel derives the following subkeys:

  • The key identifier. This is not used for encryption, but rather is a value used to identify the key with which a particular file or directory is protected.
  • The file contents encryption key
  • The filenames encryption key

In contrast, the following diagram depicts the key hierarchy for FBE when hardware-wrapped keys are used:

FBE key hierarchy (with hardware-wrapped key)
Figure 2. FBE key hierarchy (with hardware-wrapped key)

Compared to the earlier case, an additional level has been added to the key hierarchy, and the file contents encryption key has been relocated. The root node still represents the key which Android passes to Linux to unlock a set of encrypted directories. However, now that key is in ephemerally-wrapped form, and in order to be used it must be passed to dedicated hardware. This hardware must implement two interfaces that take an ephemerally-wrapped key:

  • One interface to derive inline_encryption_key and directly program it into a keyslot of the inline crypto engine. This allows file contents to be encrypted/decrypted without software having access to the raw key. In the Android common kernels, this interface corresponds to the blk_ksm_ll_ops::keyslot_program operation, which must be implemented by the storage driver.
  • One interface to derive and return sw_secret ("software secret" -- also called the "raw secret" in some places), which is the key that Linux uses to derive the subkeys for everything other than file contents encryption. In the Android common kernels, this interface corresponds to the blk_ksm_ll_ops::derive_raw_secret operation, which must be implemented by the storage driver.

To derive inline_encryption_key and sw_secret from the raw storage key, the hardware must use a cryptographically strong KDF. This KDF must follow cryptography best practices; it must have a security strength of at least 256 bits, i.e. enough for any algorithm used later on. It also must use a distinct label, context, and/or application-specific information string when deriving each type of subkey in order to guarantee that the resulting subkeys are cryptographically isolated, i.e. knowledge of one of them doesn't reveal any other. Key stretching is not required, as the raw storage key is already a uniformly random key.

Technically, any KDF that meets the security requirements could be used. However, for testing purposes, it is necessary to re-implement the same KDF in test code. Currently, one KDF has been reviewed and implemented; it can be found in the source code for vts_kernel_encryption_test. It is recommended that hardware use this KDF, which uses NIST SP 800-108 "KDF in Counter Mode" with AES-256-CMAC as the PRF. Note that to be compatible, all parts of the algorithm must be identical, including the choice of KDF contexts and labels for each subkey.

Key wrapping

To meet the security goals of hardware-wrapped keys, two types of key wrapping are defined:

  • Ephemeral wrapping: the hardware encrypts the raw key using a key which is randomly generated at every boot and is not directly exposed outside the hardware.
  • Long-term wrapping: the hardware encrypts the raw key using a unique, persistent key built into the hardware which is not directly exposed outside the hardware.

All keys passed to the Linux kernel to unlock the storage are ephemerally-wrapped. This ensures that if an attacker is able to extract an in-use key from system memory, then that key will be unusable not only off-device, but also on-device after a reboot.

At the same time, Android still needs to be able to store an encrypted version of the keys on-disk so that they can be unlocked in the first place. The raw keys would work for this purpose. However, it is desirable to never have the raw keys be present in system memory at all so that they can never be extracted to be used off-device, even if extracted at boot time. For this reason, the concept of long-term wrapping is defined.

To support managing keys wrapped in these two different ways, the hardware must implement the following interfaces:

  • Interfaces to generate and import storage keys, returning them in long-term wrapped form. These interfaces are accessed indirectly through KeyMint, and they correspond to the TAG_STORAGE_KEY KeyMint tag. The "generate" ability is used by vold to generate new storage keys for use by Android, while the "import" ability is used by vts_kernel_encryption_test to import test keys.
  • An interface to convert a long-term wrapped storage key into an ephemerally-wrapped storage key. This corresponds to the convertStorageKeyToEphemeral KeyMint method. This method is used by both vold and vts_kernel_encryption_test in order to unlock the storage.

The key wrapping algorithm is an implementation detail, but it should use a strong AEAD such as AES-256-GCM with random IVs.

Software changes required

AOSP already has a basic framework for supporting hardware-wrapped keys. This includes the support in userspace components such as vold, as well as the Linux kernel support in blk-crypto, fscrypt and dm-default-key.

However, some implementation-specific changes are required.

KeyMint changes

The device's KeyMint implementation must be modified to support TAG_STORAGE_KEY and implement the convertStorageKeyToEphemeral method.

In Keymaster, exportKey was used instead of convertStorageKeyToEphemeral.

Linux kernel changes

The Linux kernel driver for the device's inline crypto engine must be modified to set BLK_CRYPTO_FEATURE_WRAPPED_KEYS, make the keyslot_program() and keyslot_evict() operations support programming/evicting hardware-wrapped keys, and implement the derive_raw_secret() operation.

Testing

Although encryption with hardware-wrapped keys is harder to test than encryption with standard keys, it is still possible to test by importing a test key and re-implementing the key derivation that the hardware does. This is implemented in vts_kernel_encryption_test. To run this test, run:

atest -v vts_kernel_encryption_test

Read the test log and verify that the hardware-wrapped key test cases (e.g. FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy and DmDefaultKeyTest.TestHwWrappedKey) were not skipped due to support for hardware-wrapped keys not being detected, as the test results will still be "passed" in that case.

Enabling

Once the device's hardware-wrapped key support is working correctly, you can make the following changes to the device's fstab file to make Android use it for FBE and metadata encryption:

  • FBE: add the wrappedkey_v0 flag to the fileencryption parameter. For example, use fileencryption=::inlinecrypt_optimized+wrappedkey_v0. For more details, see the FBE documentation.
  • Metadata encryption: add the wrappedkey_v0 flag to the metadata_encryption parameter. For example, use metadata_encryption=:wrappedkey_v0. For more details, see the metadata encryption documentation.