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 might exceed the number of keyslots.
- Inline crypto engines typically lose the contents of their keyslots if the storage controller (usually UFS or eMMC) is reset. Resetting the storage controller is a standard error recovery procedure that is executed if certain types of storage errors occur, and such errors can occur at any time. Therefore, when inline crypto is being used, the operating system must always be ready to reprogram the keyslots without user intervention.
- 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:
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:
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 theblk_crypto_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 theblk_crypto_ll_ops::derive_sw_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, that is, enough for any algorithm used later on. It also must use a
distinct label, context, and app-specific information string when
deriving each type of subkey in order to guarantee that the resulting subkeys
are cryptographically isolated, that is, 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's 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 is 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's 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 byvold
to generate new storage keys for use by Android, while the "import" ability is used byvts_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 bothvold
andvts_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 support hardware-wrapped keys.
For android14
and higher kernels,
set BLK_CRYPTO_KEY_TYPE_HW_WRAPPED
in blk_crypto_profile::key_types_supported
,
make blk_crypto_ll_ops::keyslot_program
and blk_crypto_ll_ops::keyslot_evict
support programming/evicting hardware-wrapped keys,
and implement blk_crypto_ll_ops::derive_sw_secret
.
For android12
and android13
kernels,
set BLK_CRYPTO_FEATURE_WRAPPED_KEYS
in blk_keyslot_manager::features
,
make blk_ksm_ll_ops::keyslot_program
and blk_ksm_ll_ops::keyslot_evict
support programming/evicting hardware-wrapped keys,
and implement blk_ksm_ll_ops::derive_raw_secret
.
For android11
kernels,
set BLK_CRYPTO_FEATURE_WRAPPED_KEYS
in keyslot_manager::features
,
make keyslot_mgmt_ll_ops::keyslot_program
and keyslot_mgmt_ll_ops::keyslot_evict
support programming/evicting hardware-wrapped keys,
and implement keyslot_mgmt_ll_ops::derive_raw_secret
.
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 (for example,
FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy
and
DmDefaultKeyTest.TestHwWrappedKey
) were not skipped due to support
for hardware-wrapped keys not being detected, as the test results are still
"passed" in that case.
Enable keys
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 thefileencryption
parameter. For example, usefileencryption=::inlinecrypt_optimized+wrappedkey_v0
. For more details, see the FBE documentation. - Metadata encryption: add the
wrappedkey_v0
flag to themetadata_encryption
parameter. For example, usemetadata_encryption=:wrappedkey_v0
. For more details, see the metadata encryption documentation.