Claves unidas con hardware

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, se desea 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 claves envueltas en hardware, cuando hay compatibilidad con el hardware. Las claves protegidas por 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 protegido (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 dirigen 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 de 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 podría 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 criptográficos 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 este 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:

Jerarquía de claves de FBE (estándar)
Figura 1: Jerarquía de claves del FBE (estándar)

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 específico de directorios encriptados, como el almacenamiento encriptado con credenciales para un usuario específico de Android. (En el kernel, esta clave se denomina clave maestra de fscrypt). A partir de esta clave, el kernel deriva las siguientes subclaves:

  • Es el identificador de la 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 el FBE cuando se usan claves encapsuladas en hardware:

Jerarquía de claves de FBE (con clave protegida por hardware)
Figura 2: Jerarquía de claves de FBE (con clave protegida por 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 unida de forma efímera:

  • Una interfaz para derivar inline_encryption_key y 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ón blk_crypto_ll_ops::keyslot_program, que debe implementar el controlador de almacenamiento.
  • Una interfaz para derivar y devolver sw_secret ("secreto de software", también llamado "secreto sin procesar" en algunos lugares), 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ón blk_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 fortaleza de 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. En el caso de 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.
  • Unión 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 de él.

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. Se accede a estas interfaces de forma indirecta a través de KeyMint, y corresponden a la etiqueta TAG_STORAGE_KEY de KeyMint. vold usa la capacidad de "generar" para generar claves de almacenamiento nuevas para que las use Android, mientras que vts_kernel_encryption_test usa la capacidad de "importar" para importar claves de prueba.
  • Es una interfaz para convertir una clave de almacenamiento unida a largo plazo en una clave de almacenamiento unida de forma efímera. Esto corresponde al método convertStorageKeyToEphemeral de KeyMint. vold y vts_kernel_encryption_test usan este método 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 protegidas por 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.

Sin embargo, se requieren algunos cambios específicos de la implementación.

Cambios en KeyMint

La implementación de KeyMint del dispositivo debe modificarse para admitir TAG_STORAGE_KEY y, además, implementar el método convertStorageKeyToEphemeral.

En Keymaster, se usó exportKey en lugar de convertStorageKeyToEphemeral.

Cambios en el kernel de Linux

Se debe modificar el controlador del kernel de Linux para el motor criptográfico integrado del dispositivo para que admita claves encapsuladas en hardware.

Para los kernels de android14 y versiones posteriores, establece BLK_CRYPTO_KEY_TYPE_HW_WRAPPED en blk_crypto_profile::key_types_supported, haz que blk_crypto_ll_ops::keyslot_program y blk_crypto_ll_ops::keyslot_evict admitan la programación o el desalojo de claves envueltas en hardware, y, luego, implementa blk_crypto_ll_ops::derive_sw_secret.

Para los kernels android12 y android13, establece BLK_CRYPTO_FEATURE_WRAPPED_KEYS en blk_keyslot_manager::features, haz que blk_ksm_ll_ops::keyslot_program y blk_ksm_ll_ops::keyslot_evict admitan la programación o el desalojo de claves envueltas en hardware, y, luego, implementa blk_ksm_ll_ops::derive_raw_secret.

Para los kernels de android11, establece BLK_CRYPTO_FEATURE_WRAPPED_KEYS en keyslot_manager::features, haz que keyslot_mgmt_ll_ops::keyslot_program y keyslot_mgmt_ll_ops::keyslot_evict admitan la programación o la expulsión de claves envueltas en hardware, y, luego, implementa keyslot_mgmt_ll_ops::derive_raw_secret.

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 a la que llama kdf1. Este KDF pertenece a la familia de KDF en modo de 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 clave secundaria. 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.

Cómo habilitar 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_v0 al parámetro fileencryption. Por ejemplo, usa fileencryption=::inlinecrypt_optimized+wrappedkey_v0. Para obtener más detalles, consulta la documentación de FBE.
  • Encriptación de metadatos: Agrega la marca wrappedkey_v0 al parámetro metadata_encryption. Por ejemplo, usa metadata_encryption=:wrappedkey_v0. Para obtener más detalles, consulta la documentación sobre la encriptación de metadatos.