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 aren't 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 known in raw form only to dedicated hardware; software sees and works with these keys only 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's 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 host controller is reset. Resetting the storage host 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 be used only 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 to do this other work.
To avoid these problems, the storage keys are instead made into hardware-wrapped keys, which can be unwrapped and used only 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 key derivation function (KDF) 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 isn't 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_keyand 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_programoperation, which must be implemented by the storage driver. - One interface to derive and return
sw_secret("software secret" -- previously called the "raw secret"), 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_secretoperation, 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 and context when deriving each type of subkey to guarantee that
the resulting subkeys are cryptographically isolated, that is, knowledge of one
of them doesn't reveal any other. Key stretching isn't 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, vts_kernel_encryption_test
implements the same KDF in software to reproduce the on-disk ciphertext
and verify that it's correct. For ease of testing and to ensure that a secure
and already-reviewed KDF is used, we recommend that hardware implement the
default KDF that the test checks for. For hardware that uses a different KDF,
see Test wrapped keys for how to configure the test
accordingly.
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 isn't directly exposed outside the hardware.
- Long-term wrapping: the hardware encrypts the raw key using a unique, persistent key built into the hardware which isn't 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. The generate interface is used by
voldto generate new storage keys for use by Android. The import interface is used byvts_kernel_encryption_testto import test keys. - An interface to convert a long-term wrapped storage key into an
ephemerally-wrapped storage key. This interface is used by both
voldandvts_kernel_encryption_testto 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.
Linux kernel changes
The Linux kernel driver for the device's storage controller with inline encryption support must be modified to support hardware-wrapped keys.
For android17 and higher kernels:
- Set
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDinblk_crypto_profile::key_types_supported. - Make
blk_crypto_ll_ops::keyslot_programsupport programming hardware-wrapped keys. - Make
blk_crypto_ll_ops::keyslot_evictsupport evicting hardware-wrapped keys. - Implement
blk_crypto_ll_ops::derive_sw_secret,blk_crypto_ll_ops::import_key,blk_crypto_ll_ops::generate_key, andblk_crypto_ll_ops::prepare_key.
For android14, android15, and android16
kernels:
- Set
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDinblk_crypto_profile::key_types_supported. - Make
blk_crypto_ll_ops::keyslot_programsupport programming hardware-wrapped keys. - Make
blk_crypto_ll_ops::keyslot_evictsupport evicting hardware-wrapped keys. - Implement
blk_crypto_ll_ops::derive_sw_secret.
For android12 and android13 kernels:
- Set
BLK_CRYPTO_FEATURE_WRAPPED_KEYSinblk_keyslot_manager::features. - Make
blk_ksm_ll_ops::keyslot_programsupport programming hardware-wrapped keys. - Make
blk_ksm_ll_ops::keyslot_evictsupport evicting hardware-wrapped keys. - Implement
blk_ksm_ll_ops::derive_raw_secret.
For android11 kernels:
- Set
BLK_CRYPTO_FEATURE_WRAPPED_KEYSinkeyslot_manager::features. - Make
keyslot_mgmt_ll_ops::keyslot_programsupport programming hardware-wrapped keys. - Make
keyslot_mgmt_ll_ops::keyslot_evictsupport evicting hardware-wrapped keys. - Implement
keyslot_mgmt_ll_ops::derive_raw_secret.
KeyMint changes (legacy)
In the current version of hardware-wrapped keys (wrappedkey), the
generation, import, and preparation of hardware-wrapped keys use the Linux
kernel ioctls BLKCRYPTOGENERATEKEY,
BLKCRYPTOIMPORTKEY, and BLKCRYPTOPREPAREKEY. These
ioctls correspond to methods in struct blk_crypto_ll_ops. The
storage driver implements these methods and communicates with the key wrapping
hardware to perform the requested operation. For more information about these
ioctls, see the
Linux kernel documentation..
These ioctls were added in Linux 6.16. On devices that didn't launch with the
ioctl-based solution, a different solution using Android KeyMint (or previously
KeyMaster) is used. The legacy solution (wrappedkey_v0) isn't
compatible with the mainline Linux kernel or with the current solution. The
legacy solution uses the following KeyMint functionality:
- Support for
TAG_STORAGE_KEY, both for key generation and import. - Support for the
convertStorageKeyToEphemeralmethod.
This KeyMint functionality is needed only on devices that use the legacy
solution, corresponding to wrappedkey_v0 in the fstab file.
Devices that use the current solution, corresponding to wrappedkey
in the fstab file, don't need this KeyMint functionality to be implemented.
Test wrapped keys
Although encryption with hardware-wrapped keys is harder to test than encryption
with raw keys, it's 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) weren't skipped due to support
for hardware-wrapped keys not being detected, as the test results are still
"passed" in that case.
By default, vts_kernel_encryption_test assumes the hardware
implements a KDF that it calls kdf1. This
KDF belongs to the counter mode family of KDFs from NIST
SP 800-108, and it uses AES-256-CMAC as the pseudorandom function. For more
information about CMAC, see the CMAC
specification. The KDF uses specific contexts and labels when deriving each
subkey. Hardware should implement this KDF, including the exact choice of
context, label, and formatting of the fixed input string when deriving each
subkey.
However, vts_kernel_encryption_test also implements additional KDFs
kdf2 through kdf4. These are equally as secure as
kdf1 and differ only in the choice of contexts, labels, and
formatting of the fixed input string. They exist only to accommodate different
hardware.
For devices that use a different KDF, set the
ro.crypto.hw_wrapped_keys.kdf system property in
PRODUCT_VENDOR_PROPERTIES to the name of the KDF as defined in the
test source code. This causes vts_kernel_encryption_test to check
for that KDF instead of kdf1. For example, to select
kdf2, use:
PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2
For devices that use a KDF that the test doesn't support, also add an implementation of that KDF to the test and give it a unique name.
Enable wrapped keys
When the device's hardware-wrapped key support is working correctly, make the
following changes to the device's fstab file to make Android use it
for FBE and metadata encryption:
- FBE: add the
wrappedkey(orwrappedkey_v0for the legacy version) flag to thefileencryptionparameter. For example, usefileencryption=::inlinecrypt_optimized+wrappedkey. For more details, see the FBE documentation. - Metadata encryption: add the
wrappedkeyflag (orwrappedkey_v0for the legacy version) to themetadata_encryptionparameter. For example, usemetadata_encryption=:wrappedkey. For more details, see the metadata encryption documentation.
In each case, there are two versions of the flag:
wrappedkey, supported by Android 17 and higher, enables the current version of hardware-wrapped keys. This version is compatible with the mainline Linux kernel.wrappedkey_v0, supported by Android 11 and higher, enables the legacy version of hardware-wrapped keys. This version isn't compatible with the mainline Linux kernel. It proxies certain operations through KeyMint and uses a nonstandard on-disk format. For more information, see KeyMint changes (legacy).
On devices launching with Android 17 or higher, prefer wrappedkey.
On devices that already launched with wrappedkey_v0, continue using
wrappedkey_v0 for backwards compatibility.