Al igual que la mayoría del software de encriptación de discos y archivos, la encriptación de almacenamiento de Android tradicionalmente se basa en la presencia de claves de encriptación sin procesar en la memoria del sistema para que se pueda realizar la encriptación. Incluso cuando la encriptación se realiza con hardware dedicado en lugar de software, el software generalmente debe administrar las claves de encriptación sin procesar.
Tradicionalmente, esto no se considera un problema porque las claves no están presentes durante un ataque sin conexión, que es el principal tipo de ataque contra el que se pretende proteger la encriptación del almacenamiento. Sin embargo, existe el deseo de brindar mayor protección contra otros tipos de ataques, como los ataques de arranque en frío y los ataques en línea en los que un atacante podría filtrar la memoria del sistema sin comprometer por completo el dispositivo.
Para resolver este problema, Android 11 introdujo la compatibilidad con las claves protegidas por hardware, cuando hay compatibilidad con el hardware. Las claves unidas al hardware son claves de almacenamiento que solo el hardware dedicado conoce en formato sin procesar. El software ve y usa estas claves solo en formato unido (encriptado). Este hardware debe ser capaz de generar e importar claves de almacenamiento, envolver claves de almacenamiento en formas efímeras y a largo plazo, derivar subclaves, programar directamente una subclave en un motor criptográfico intercalado y devolver una subclave independiente al software.
Nota: Un motor criptográfico integrado (o hardware de encriptación integrado) hace referencia al hardware que encripta o desencripta datos mientras se transfieren hacia o desde el dispositivo de almacenamiento. Por lo general, se trata de un controlador host de UFS o eMMC que implementa las extensiones criptográficas definidas por la especificación JEDEC correspondiente.
Diseño
En esta sección, se presenta el diseño de la función de claves protegidas por hardware, incluido el soporte de hardware que se requiere para ella. Este análisis se centra en la encriptación basada en archivos (FBE), pero la solución también se aplica a la encriptación de metadatos.
Una forma de evitar la necesidad de tener las claves de encriptación sin procesar en la memoria del sistema sería mantenerlas solo en las ranuras de claves de un motor criptográfico integrado. Sin embargo, este enfoque presenta algunos problemas:
- La cantidad de claves de encriptación puede exceder la cantidad de ranuras para claves.
- Por lo general, los motores criptográficos integrados pierden el contenido de sus ranuras de claves si se restablece el controlador del host de almacenamiento. El restablecimiento del controlador del host de almacenamiento es un procedimiento estándar de recuperación de errores que se ejecuta si se producen ciertos tipos de errores de almacenamiento, y estos errores pueden ocurrir en cualquier momento. Por lo tanto, cuando se usa la criptografía intercalada, el sistema operativo siempre debe estar listo para reprogramar las ranuras de claves sin intervención del usuario.
- Los motores de criptografía intercalados solo se pueden usar para encriptar o desencriptar bloques completos de datos en el disco. Sin embargo, en el caso de FBE, el software aún debe poder realizar otro trabajo criptográfico, como la encriptación de nombres de archivos y la derivación de identificadores de claves. El software aún necesitaría acceso a las claves sin procesar de FBE para realizar ese otro trabajo.
Para evitar estos problemas, las claves de almacenamiento se convierten en claves protegidas por hardware, que solo pueden separarse y usarse con hardware dedicado. Esto permite admitir una cantidad ilimitada de claves. Además, se modifica la jerarquía de claves y se traslada parcialmente a este hardware, lo que permite que se devuelva una clave secundaria al software para tareas que no pueden usar un motor criptográfico integrado.
Jerarquía de claves
Las claves se pueden derivar de otras claves con una función de derivación de claves (KDF), como HKDF, lo que genera una jerarquía de claves.
En el siguiente diagrama, se muestra una jerarquía de claves típica para la FBE cuando no se usan claves protegidas por hardware:
La clave de clase de FBE es la clave de encriptación sin procesar que Android pasa al kernel de Linux para desbloquear un conjunto particular de directorios encriptados, como el almacenamiento encriptado con credenciales para un usuario de Android en particular. (En el kernel, esta clave se denomina clave maestra de fscrypt). A partir de esta clave, el kernel deriva las siguientes subclaves:
- El identificador de clave. No se usa para la encriptación, sino que es un valor que se usa para identificar la clave con la que se protege un archivo o directorio en particular.
- Clave de encriptación del contenido del archivo
- Clave de encriptación de nombres de archivos
En cambio, el siguiente diagrama muestra la jerarquía de claves para la FBE cuando se usan claves encapsuladas en hardware:
En comparación con el caso anterior, se agregó un nivel adicional a la jerarquía de claves y se reubicó la clave de encriptación del contenido del archivo. El nodo raíz sigue representando la clave que Android pasa a Linux para desbloquear un conjunto de directorios encriptados. Sin embargo, ahora esa clave está en formato unido de forma efímera y, para usarse, debe pasarse a hardware dedicado. Este hardware debe implementar dos interfaces que tomen una clave envuelta de forma efímera:
- Una interfaz para derivar
inline_encryption_keyy programarlo directamente en una ranura de clave del motor criptográfico intercalado. Esto permite que el contenido de los archivos se encripte o desencripte sin que el software tenga acceso a la clave sin procesar. En los kernels comunes de Android, esta interfaz corresponde a la operaciónblk_crypto_ll_ops::keyslot_program, que debe implementar el controlador de almacenamiento. - Una interfaz para derivar y devolver
sw_secret("secreto de software", antes llamado "secreto sin procesar"), que es la clave que Linux usa para derivar las subclaves para todo lo que no sea la encriptación del contenido del archivo. En los kernels comunes de Android, esta interfaz corresponde a la operaciónblk_crypto_ll_ops::derive_sw_secret, que debe implementar el controlador de almacenamiento.
Para derivar inline_encryption_key y sw_secret de la clave de almacenamiento sin procesar, el hardware debe usar una KDF criptográficamente sólida. Este KDF debe seguir las prácticas recomendadas de criptografía y tener una seguridad de al menos 256 bits, es decir, suficiente para cualquier algoritmo que se use más adelante. También debe usar una etiqueta y un contexto distintos cuando deriva cada tipo de clave secundaria para garantizar que las claves secundarias resultantes estén aisladas de forma criptográfica, es decir, el conocimiento de una de ellas no revela ninguna otra. No se requiere el estiramiento de la clave, ya que la clave de almacenamiento sin procesar ya es una clave aleatoria uniforme.
Técnicamente, se podría usar cualquier KDF que cumpla con los requisitos de seguridad.
Sin embargo, para fines de prueba, vts_kernel_encryption_test implementa el mismo KDF en el software para reproducir el texto cifrado en el disco y verificar que sea correcto. Para facilitar las pruebas y garantizar que se use una KDF segura y ya revisada, recomendamos que el hardware implemente la KDF predeterminada que verifica la prueba. Para el hardware que usa un KDF diferente, consulta Cómo probar claves encapsuladas para saber cómo configurar la prueba de manera adecuada.
Unión de claves
Para cumplir con los objetivos de seguridad de las claves unidas por hardware, se definen dos tipos de unión de claves:
- Ajuste efímero: El hardware encripta la clave sin procesar con una clave que se genera de forma aleatoria en cada inicio y no se expone directamente fuera del hardware.
- Ajuste a largo plazo: El hardware encripta la clave sin procesar con una clave persistente y única integrada en el hardware que no se expone directamente fuera del hardware.
Todas las claves que se pasan al kernel de Linux para desbloquear el almacenamiento se encapsulan de forma efímera. Esto garantiza que, si un atacante puede extraer una clave en uso de la memoria del sistema, esa clave no se pueda usar no solo fuera del dispositivo, sino también en el dispositivo después de un reinicio.
Al mismo tiempo, Android debe poder almacenar una versión encriptada de las claves en el disco para que se puedan desbloquear. Las claves sin procesar servirían para este propósito. Sin embargo, es deseable que las claves sin procesar nunca estén presentes en la memoria del sistema para que nunca se puedan extraer y usar fuera del dispositivo, incluso si se extraen en el momento del inicio. Por este motivo, se define el concepto de envoltorio a largo plazo.
Para admitir la administración de claves encapsuladas de estas dos formas diferentes, el hardware debe implementar las siguientes interfaces:
- Son interfaces para generar e importar claves de almacenamiento, que las devuelven en formato unido a largo plazo.
voldusa la interfaz generate para generar nuevas claves de almacenamiento que Android pueda usar.vts_kernel_encryption_testusa la interfaz de importación para importar claves de prueba. - Interfaz para convertir una clave de almacenamiento unida a largo plazo en una clave de almacenamiento unida de forma efímera.
voldyvts_kernel_encryption_testusan esta interfaz para desbloquear el almacenamiento.
El algoritmo de unión de claves es un detalle de implementación, pero debe usar un AEAD sólido, como AES-256-GCM con IV aleatorios.
Se requieren cambios de software
El AOSP ya tiene un framework básico para admitir claves envueltas en hardware. Esto incluye la compatibilidad en componentes del espacio del usuario, como vold, así como la compatibilidad del kernel de Linux en blk-crypto, fscrypt y dm-default-key.
Cambios en el kernel de Linux
El controlador del kernel de Linux para el controlador de almacenamiento del dispositivo con compatibilidad con la encriptación intercalada debe modificarse para admitir claves encapsuladas en hardware.
Para kernels android17 y versiones posteriores:
- Establece
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDenblk_crypto_profile::key_types_supported. - Hacer que
blk_crypto_ll_ops::keyslot_programadmita la programación de claves envueltas en hardware - Hacer que
blk_crypto_ll_ops::keyslot_evictadmita la expulsión de claves protegidas por hardware - Implementa
blk_crypto_ll_ops::derive_sw_secret,blk_crypto_ll_ops::import_key,blk_crypto_ll_ops::generate_keyyblk_crypto_ll_ops::prepare_key.
Para los kernels de android14, android15 y android16, haz lo siguiente:
- Establece
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDenblk_crypto_profile::key_types_supported. - Hacer que
blk_crypto_ll_ops::keyslot_programadmita la programación de claves envueltas en hardware - Hacer que
blk_crypto_ll_ops::keyslot_evictadmita la expulsión de claves protegidas por hardware - Implementar
blk_crypto_ll_ops::derive_sw_secret
Para los kernels de android12 y android13:
- Establece
BLK_CRYPTO_FEATURE_WRAPPED_KEYSenblk_keyslot_manager::features. - Hacer que
blk_ksm_ll_ops::keyslot_programadmita la programación de claves envueltas en hardware - Hacer que
blk_ksm_ll_ops::keyslot_evictadmita la expulsión de claves protegidas por hardware - Implementar
blk_ksm_ll_ops::derive_raw_secret
Para los kernels de android11:
- Establece
BLK_CRYPTO_FEATURE_WRAPPED_KEYSenkeyslot_manager::features. - Hacer que
keyslot_mgmt_ll_ops::keyslot_programadmita la programación de claves envueltas en hardware - Hacer que
keyslot_mgmt_ll_ops::keyslot_evictadmita la expulsión de claves protegidas por hardware - Implementar
keyslot_mgmt_ll_ops::derive_raw_secret
Cambios en KeyMint (heredado)
En la versión actual de las claves unidas por hardware (wrappedkey), la generación, la importación y la preparación de las claves unidas por hardware usan los ioctl del kernel de Linux BLKCRYPTOGENERATEKEY, BLKCRYPTOIMPORTKEY y BLKCRYPTOPREPAREKEY. Estos ioctl corresponden a métodos en struct blk_crypto_ll_ops. El controlador de almacenamiento implementa estos métodos y se comunica con el hardware de encapsulamiento de claves para realizar la operación solicitada. Para obtener más información sobre estos ioctls, consulta la documentación del kernel de Linux.
Estos ioctl se agregaron en Linux 6.16. En los dispositivos que no se lanzaron con la solución basada en ioctl, se usa una solución diferente con Android KeyMint (o KeyMaster anteriormente). La solución heredada (wrappedkey_v0) no es compatible con el kernel principal de Linux ni con la solución actual. La solución heredada usa la siguiente funcionalidad de KeyMint:
- Compatibilidad con
TAG_STORAGE_KEY, tanto para la generación como para la importación de claves. - Se agregó compatibilidad con el método
convertStorageKeyToEphemeral.
Esta funcionalidad de KeyMint solo se necesita en dispositivos que usan la solución heredada, que corresponde a wrappedkey_v0 en el archivo fstab.
Los dispositivos que usan la solución actual, que corresponde a wrappedkey en el archivo fstab, no necesitan que se implemente esta funcionalidad de KeyMint.
Prueba claves unidas
Si bien la encriptación con claves unidas por hardware es más difícil de probar que la encriptación con claves sin procesar, aún es posible realizar pruebas importando una clave de prueba y volviendo a implementar la derivación de claves que realiza el hardware. Esto se implementa en vts_kernel_encryption_test. Para ejecutar esta prueba, ejecuta el siguiente comando:
atest -v vts_kernel_encryption_test
Lee el registro de pruebas y verifica que no se hayan omitido los casos de prueba de claves protegidas por hardware (por ejemplo, FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy y DmDefaultKeyTest.TestHwWrappedKey) porque no se detectó la compatibilidad con claves protegidas por hardware, ya que, en ese caso, los resultados de las pruebas seguirán siendo "aprobados".
De forma predeterminada, vts_kernel_encryption_test supone que el hardware implementa una KDF que denomina kdf1. Este KDF pertenece a la familia de KDF en modo contador de NIST SP 800-108 y usa AES-256-CMAC como la función seudoaleatoria. Para obtener más información sobre CMAC, consulta la especificación de CMAC. El KDF usa contextos y etiquetas específicos cuando deriva cada subclave. El hardware debe implementar esta KDF, incluida la elección exacta del contexto, la etiqueta y el formato de la cadena de entrada fija cuando se deriva cada clave secundaria.
Sin embargo, vts_kernel_encryption_test también implementa KDF adicionales kdf2 a través de kdf4. Son igual de seguros que kdf1 y solo difieren en la elección de contextos, etiquetas y formato de la cadena de entrada fija. Solo existen para adaptarse a diferentes hardware.
En el caso de los dispositivos que usan un KDF diferente, configura la propiedad del sistema ro.crypto.hw_wrapped_keys.kdf en PRODUCT_VENDOR_PROPERTIES con el nombre del KDF tal como se define en el código fuente de la prueba. Esto hace que vts_kernel_encryption_test verifique ese KDF en lugar de kdf1. Por ejemplo, para seleccionar kdf2, usa lo siguiente:
PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2
Para los dispositivos que usan un KDF que la prueba no admite, también agrega una implementación de ese KDF a la prueba y asígnale un nombre único.
Habilita las llaves unidas
Cuando la compatibilidad con claves encapsuladas en hardware del dispositivo funcione correctamente, realiza los siguientes cambios en el archivo fstab del dispositivo para que Android lo use para la encriptación de FBE y de metadatos:
- FBE: Agrega la marca
wrappedkey(owrappedkey_v0para la versión heredada) al parámetrofileencryption. Por ejemplo, usafileencryption=::inlinecrypt_optimized+wrappedkey. Para obtener más detalles, consulta la documentación de FBE. - Encriptación de metadatos: Agrega la marca
wrappedkey(owrappedkey_v0para la versión heredada) al parámetrometadata_encryption. Por ejemplo, usametadata_encryption=:wrappedkey. Para obtener más detalles, consulta la documentación sobre la encriptación de metadatos.
En cada caso, hay dos versiones de la marca:
wrappedkey, compatible con Android 17 y versiones posteriores, habilita la versión actual de las claves encapsuladas en hardware. Esta versión es compatible con el kernel de Linux principal.wrappedkey_v0, compatible con Android 11 y versiones posteriores, habilita la versión heredada de las claves protegidas por hardware. Esta versión no es compatible con el kernel principal de Linux. Proxyiza ciertas operaciones a través de KeyMint y usa un formato no estándar en el disco. Para obtener más información, consulta Cambios en KeyMint (versión heredada).
En los dispositivos que se lancen con Android 17 o versiones posteriores, se recomienda usar wrappedkey.
En los dispositivos que ya se lanzaron con wrappedkey_v0, sigue usando wrappedkey_v0 para la retrocompatibilidad.