Llaves unidas en hardware

Al igual que la mayoría de los software de encriptación de archivos y discos, la encriptación de almacenamiento de Android tradicionalmente se basa en que las claves de encriptación sin procesar estén presentes en la memoria del sistema para poder realizar la encriptación. Incluso cuando la encriptación se realiza con hardware dedicado en lugar de software, este generalmente aún debe administrar las claves de encriptación sin procesar.

Tradicionalmente, esto no se considera un problema porque las claves no estarán presentes durante un ataque sin conexión, que es el tipo de ataque principal contra el que se diseñó la encriptación del almacenamiento. Sin embargo, se desea proporcionar una mayor protección contra otros tipos de ataques, como los ataques de inicio en frío y los ataques en línea en los que un atacante podría filtrar la memoria del sistema sin vulnerar por completo el dispositivo.

Para resolver este problema, Android 11 introdujo la compatibilidad con llaves empaquetadas en hardware, donde hay compatibilidad con el hardware. Las claves empaquetadas en hardware son claves de almacenamiento que solo se conocen en formato sin procesar para el hardware dedicado. El software solo ve estas claves y funciona con ellas en formato empaquetado (encriptado). Este hardware debe ser capaz de generar e importar claves de almacenamiento, unir claves de almacenamiento en formas efímeras y a largo plazo, derivar subclaves, programar directamente una subclave en un motor de criptografía intercalado y mostrar una subclave independiente al software.

Nota: Un motor de criptografía intercalado (o hardware de encriptación intercalado) hace referencia al hardware que encripta o desencripta datos mientras se envían o reciben desde el dispositivo de almacenamiento. Por lo general, se trata de un controlador de host UFS o eMMC que implementa las extensiones de criptografía definidas por la especificación JEDEC correspondiente.

Diseño

En esta sección, se presenta el diseño de la función de claves empaquetadas en hardware, incluida la compatibilidad de hardware necesaria 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 las claves de encriptación sin procesar en la memoria del sistema es mantenerlas solo en las ranuras de claves de un cripto Engine intercalado. Sin embargo, este enfoque tiene algunos problemas:

  • Es posible que la cantidad de claves de encriptación supere la cantidad de ranuras de claves.
  • Por lo general, los motores de criptografía intercalados pierden el contenido de sus ranuras de claves si se restablece el controlador de almacenamiento (por lo general, UFS o eMMC). Restablecer el controlador 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 pueden ocurrir en cualquier momento. Por lo tanto, cuando se usa la criptografía intercalada, el sistema operativo siempre debe estar listo para reprogramar los espacios 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 la FBE, el software aún debe poder realizar otras tareas criptográficas, 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 FBE sin procesar para realizar esta otra tarea.

Para evitar estos problemas, las claves de almacenamiento se convierten en claves unidas en hardware, que solo pueden separarse y usarse con hardware dedicado. Esto permite admitir una cantidad ilimitada de claves. Además, la jerarquía de claves se modifica y se traslada parcialmente a este hardware, lo que permite que se muestre una subclave al software para tareas que no pueden usar un criptomotor intercalado.

Jerarquía de claves

Las claves se pueden derivar de otras claves con una KDF (función de derivación de claves), como HKDF, lo que da como resultado 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 unidas de hardware:

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

La clave de clase 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 de 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. Esto no se usa en 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.
  • La clave de encriptación del contenido del archivo
  • La clave de encriptación de los nombres de archivo

Por el contrario, en el siguiente diagrama, se muestra la jerarquía de claves para la FBE cuando se usan claves empaquetadas en hardware:

Jerarquía de claves de FBE (con clave unida al hardware)
Figura 2: Jerarquía de claves de FBE (con clave unida 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á unida de forma efímera y, para usarla, se debe pasar al 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 programarla directamente en un espacio de claves del motor de criptografía 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 el controlador de almacenamiento debe implementar.
  • Una interfaz para derivar y mostrar sw_secret ("secreto de software", también llamado "secreto sin procesar" en algunos lugares), que es la clave que usa Linux para derivar las subclaves de todo lo que no sea la encriptación del contenido de los archivos. En los kernels comunes de Android, esta interfaz corresponde a la operación blk_crypto_ll_ops::derive_sw_secret, que el controlador de almacenamiento debe implementar.

Para derivar inline_encryption_key y sw_secret de la clave de almacenamiento sin procesar, el hardware debe usar un KDF criptográficamente seguro. 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, un contexto y una cadena de información específica de la app distinta cuando derive cada tipo de subclave para garantizar que las subclaves resultantes estén aisladas criptográficamente, es decir, que el conocimiento de una de ellas no revele ninguna otra. No se requiere el estiramiento de claves, ya que la clave de almacenamiento sin procesar ya es una clave aleatoria uniforme.

Técnicamente, se puede usar cualquier KDF que cumpla con los requisitos de seguridad. Sin embargo, para realizar pruebas, es necesario volver a implementar el mismo KDF en el código de prueba. Actualmente, se revisó y se implementó un KDF; se puede encontrar en el código fuente de vts_kernel_encryption_test. Se recomienda que el hardware use este KDF, que usa NIST SP 800-108 "KDF en modo contador" con AES-256-CMAC como PRF. Ten en cuenta que, para que sea compatible, todas las partes del algoritmo deben ser idénticas, incluida la elección de contextos de KDF y etiquetas para cada subclave.

Unión de claves

Para cumplir con los objetivos de seguridad de las claves vinculadas con hardware, se definen dos tipos de unión de claves:

  • Unión efímera: El hardware encripta la clave sin procesar con una clave que se genera de forma aleatoria en cada inicio y que no se expone directamente fuera del hardware.
  • Unión a largo plazo: El hardware encripta la clave sin procesar con una clave única y persistente 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 unen de forma efímera. Esto garantiza que, si un atacante puede extraer una clave en uso de la memoria del sistema, esa clave no solo se podrá usar 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 en primer lugar. Las claves sin procesar funcionarían para este propósito. Sin embargo, es conveniente 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 durante el inicio. Por este motivo, se define el concepto de vinculación a largo plazo.

Para admitir la administración de claves empaquetadas de estas dos maneras diferentes, el hardware debe implementar las siguientes interfaces:

  • Son interfaces para generar e importar claves de almacenamiento y devolverlas en forma unida a largo plazo. Se puede acceder a estas interfaces de forma indirecta a través de KeyMint, y corresponden a la etiqueta TAG_STORAGE_KEY de KeyMint. vold usa la función "generar" para generar claves de almacenamiento nuevas que usará Android, mientras que vts_kernel_encryption_test usa la función "importar" para importar claves de prueba.
  • 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 KeyMint convertStorageKeyToEphemeral. 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 IVs aleatorios.

Se requieren cambios en el software

AOSP ya tiene un framework básico para admitir claves unidas en hardware. Esto incluye la compatibilidad con componentes del espacio de usuario, como vold, así como la compatibilidad con el 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 e 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 de kernel de Linux para el motor de criptografía intercalado del dispositivo para admitir claves empaquetadas en hardware.

Para kernels android14 y versiones posteriores, configura 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 expulsión de claves empaquetadas en hardware, y, luego, implementa blk_crypto_ll_ops::derive_sw_secret.

Para los kernels android12 y android13, configura 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 sean compatibles con la programación o la expulsión de claves unidas por hardware y, luego, implementa blk_ksm_ll_ops::derive_raw_secret.

Para los kernels android11, configura 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 expulsión de claves empaquetadas en hardware, y, luego, implementa keyslot_mgmt_ll_ops::derive_raw_secret.

Prueba

Aunque la encriptación con claves unidas en hardware es más difícil de probar que la encriptación con claves estándar, es posible probarla mediante la importación de una clave de prueba y la reimplementación de la derivación de claves que hace el hardware. Esto se implementa en vts_kernel_encryption_test. Para ejecutar esta prueba, ejecuta lo siguiente:

atest -v vts_kernel_encryption_test

Lee el registro de prueba y verifica que no se hayan omitido los casos de prueba de claves empaquetadas en hardware (por ejemplo, FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy y DmDefaultKeyTest.TestHwWrappedKey) debido a que no se detectó la compatibilidad con las claves empaquetadas en hardware, ya que los resultados de la prueba aún se "aprueban" en ese caso.

Habilita las teclas

Una vez que la compatibilidad con la clave del dispositivo unida al hardware funcione correctamente, puedes realizar los siguientes cambios en el archivo fstab del dispositivo para que Android la use para la encriptación de FBE y 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 de encriptación de metadatos.