Como la mayoría del software de cifrado de archivos y discos, el cifrado de almacenamiento de Android tradicionalmente se basa en que las claves de cifrado sin procesar están presentes en la memoria del sistema para que se pueda realizar el cifrado. Incluso cuando el cifrado lo realiza un hardware dedicado en lugar de un software, el software generalmente aún necesita administrar las claves de cifrado sin procesar.
Tradicionalmente, esto no se ve como un problema porque las claves no estarán presentes durante un ataque fuera de línea, que es el principal tipo de ataque contra el que el cifrado de almacenamiento pretende proteger. Sin embargo, existe el deseo de brindar una mayor protección contra otros tipos de ataques, como ataques de arranque en frío y ataques en línea en los que un atacante podría perder la memoria del sistema sin comprometer completamente el dispositivo.
Para resolver este problema, Android 11 introdujo soporte para claves envueltas en hardware, donde el soporte de hardware está presente. Las claves envueltas en hardware son claves de almacenamiento que solo se conocen en forma cruda para el hardware dedicado; el software solo ve y trabaja con estas claves en forma envuelta (cifrada). 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 en línea y devolver una subclave separada al software.
Nota : Un motor criptográfico en línea (o hardware de cifrado en línea ) se refiere al hardware que cifra/descifra datos mientras se encuentran en camino hacia/desde el dispositivo de almacenamiento. Por lo general, se trata de un controlador de host UFS o eMMC que implementa las extensiones criptográficas definidas por la especificación JEDEC correspondiente.
Diseño
Esta sección presenta el diseño de la función de claves envueltas en hardware, incluido el soporte de hardware que se requiere para ello. Esta discusión se centra en el cifrado basado en archivos (FBE), pero la solución también se aplica al cifrado de metadatos .
Una forma de evitar la necesidad de claves de cifrado sin procesar en la memoria del sistema sería mantenerlas solo en las ranuras de claves de un motor criptográfico en línea. Sin embargo, este enfoque se encuentra con algunos problemas:
- El número de claves de cifrado puede exceder el número de ranuras de claves.
- Los motores criptográficos en línea solo se pueden usar para cifrar/descifrar bloques completos de datos en el disco. Sin embargo, en el caso de FBE, el software aún debe poder realizar otros trabajos criptográficos, como el cifrado de nombres de archivo y la obtención de identificadores de clave. El software aún necesitaría acceso a las claves FBE sin procesar para realizar este otro trabajo.
Para evitar estos problemas, las claves de almacenamiento se convierten en claves encapsuladas en hardware , que solo pueden ser desencapsuladas y utilizadas por hardware dedicado. Esto permite admitir un número ilimitado de claves. Además, la jerarquía de claves se modifica y se traslada parcialmente a este hardware, lo que permite devolver una subclave al software para tareas que no pueden usar un motor criptográfico en línea.
Jerarquía de claves
Las claves se pueden derivar de otras claves utilizando un KDF (función de derivación de claves) como HKDF , lo que da como resultado una jerarquía de claves .
El siguiente diagrama muestra una jerarquía de claves típica para FBE cuando no se utilizan claves envueltas en hardware:
La clave de clase FBE es la clave de cifrado sin procesar que Android pasa al kernel de Linux para desbloquear un conjunto particular de directorios cifrados, como el almacenamiento cifrado de credenciales para un usuario de Android en particular. (En el kernel, esta clave se denomina clave maestra fscrypt ). A partir de esta clave, el kernel deriva las siguientes subclaves:
- El identificador clave. Esto no se usa para el cifrado, 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 cifrado del contenido del archivo
- La clave de cifrado de nombres de archivo
Por el contrario, el siguiente diagrama muestra la jerarquía de claves para FBE cuando se utilizan claves envueltas 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 cifrado del contenido del archivo. El nodo raíz aún representa la clave que Android pasa a Linux para desbloquear un conjunto de directorios encriptados. Sin embargo, ahora esa clave está envuelta de forma efímera y, para poder usarla, debe pasarse a un hardware dedicado. Este hardware debe implementar dos interfaces que toman una clave envuelta efímeramente:
- Una interfaz para derivar
inline_encryption_key
y programarla directamente en una ranura de clave del motor criptográfico en línea. Esto permite cifrar/descifrar el contenido del archivo sin que el software tenga acceso a la clave sin procesar. En los núcleos comunes de Android, esta interfaz corresponde a la operaciónblk_ksm_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 utiliza Linux para derivar las subclaves para todo lo que no sea el cifrado del contenido del archivo. En los núcleos comunes de Android, esta interfaz corresponde a la operaciónblk_ksm_ll_ops::derive_raw_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 un KDF criptográficamente fuerte. Este KDF debe seguir las mejores prácticas de criptografía; debe tener un nivel de seguridad de al menos 256 bits, es decir, suficiente para cualquier algoritmo que se utilice posteriormente. También debe usar una etiqueta distinta, contexto y/o cadena de información específica de la aplicación al derivar cada tipo de subclave para garantizar que las subclaves resultantes estén criptográficamente aisladas, es decir, el conocimiento de una de ellas no revela ninguna otra. No es necesario ampliar la clave, ya que la clave de almacenamiento sin procesar ya es una clave uniformemente aleatoria.
Técnicamente, se podría utilizar cualquier KDF que cumpla con los requisitos de seguridad. Sin embargo, con fines de prueba, es necesario volver a implementar el mismo KDF en el código de prueba. Actualmente, se ha revisado e implementado un KDF; se puede encontrar en el código fuente de vts_kernel_encryption_test
. Se recomienda que el hardware utilice este KDF, que utiliza NIST SP 800-108 "KDF en modo contador" con AES-256-CMAC como PRF. Tenga en cuenta que para que sea compatible, todas las partes del algoritmo deben ser idénticas, incluida la elección de contextos y etiquetas KDF para cada subclave.
Envoltura de llaves
Para cumplir con los objetivos de seguridad de las claves encapsuladas en hardware, se definen dos tipos de encapsulado de claves:
- Envoltura efímera : el hardware encripta la clave sin procesar utilizando una clave que se genera aleatoriamente en cada arranque y no se expone directamente fuera del hardware.
- Envoltura a largo plazo : el hardware encripta la clave sin procesar utilizando una clave única y persistente integrada en el hardware que no está expuesta directamente fuera del hardware.
Todas las claves que se pasan al kernel de Linux para desbloquear el almacenamiento se empaquetan 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 dentro del dispositivo después de un reinicio.
Al mismo tiempo, Android aún necesita poder almacenar una versión cifrada de las claves en el disco para que puedan desbloquearse en primer lugar. Las claves sin procesar funcionarí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 para usarlas fuera del dispositivo, incluso si se extraen en el momento del arranque. Por ello, se define el concepto de envoltura a largo plazo.
Para admitir la gestión de claves envueltas en estas dos formas diferentes, el hardware debe implementar las siguientes interfaces:
- Interfaces para generar e importar claves de almacenamiento, devolviéndolas en formato empaquetado a largo plazo. A estas interfaces se accede indirectamente a través de KeyMint y corresponden a la etiqueta
TAG_STORAGE_KEY
KeyMint.vold
usa la capacidad de "generar" para generar nuevas claves de almacenamiento para que las use Android, mientras quevts_kernel_encryption_test
usa la capacidad de "importar" para importar claves de prueba. - Una interfaz para convertir una clave de almacenamiento encapsulada a largo plazo en una clave de almacenamiento encapsulada efímeramente. Esto corresponde al método
convertStorageKeyToEphemeral
KeyMint. Este método lo utilizan tantovold
comovts_kernel_encryption_test
para desbloquear el almacenamiento.
El algoritmo de envoltura de claves es un detalle de implementación, pero debe usar un AEAD fuerte como AES-256-GCM con IV aleatorios.
Se requieren cambios de software
AOSP ya tiene un marco básico para admitir claves envueltas 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 de KeyMint
La implementación de KeyMint del dispositivo debe modificarse para admitir TAG_STORAGE_KEY
e implementar el método convertStorageKeyToEphemeral
.
En Keymaster, se utilizó exportKey
en lugar de convertStorageKeyToEphemeral
.
Cambios en el kernel de Linux
El controlador del kernel de Linux para el motor criptográfico en línea del dispositivo debe modificarse para configurar BLK_CRYPTO_FEATURE_WRAPPED_KEYS
, hacer que las keyslot_program()
y keyslot_evict()
admitan la programación/desalojar claves envueltas en hardware e implementar la operación derive_raw_secret()
.
Pruebas
Aunque el cifrado con claves envueltas en hardware es más difícil de probar que el cifrado con claves estándar, todavía es posible probarlo importando una clave de prueba y volviendo a implementar la derivación de clave que hace el hardware. Esto se implementa en vts_kernel_encryption_test
. Para ejecutar esta prueba, ejecute:
atest -v vts_kernel_encryption_test
Lea el registro de prueba y verifique que los casos de prueba clave envueltos en hardware (p. ej., FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy
y DmDefaultKeyTest.TestHwWrappedKey
) no se omitieron debido a que no se detectó la compatibilidad con claves envueltas en hardware, ya que los resultados de la prueba seguirán siendo "aprobados" en Ese caso.
Habilitación
Una vez que la compatibilidad con la clave envuelta en hardware del dispositivo funcione correctamente, puede realizar los siguientes cambios en el archivo fstab
del dispositivo para que Android lo use para el cifrado de metadatos y FBE:
- FBE: agregue el indicador
wrappedkey_v0
al parámetro de cifrado defileencryption
. Por ejemplo, usefileencryption=::inlinecrypt_optimized+wrappedkey_v0
. Para obtener más detalles, consulte la documentación de FBE . - Cifrado de metadatos: agregue el indicador
wrappedkey_v0
al parámetrometadata_encryption
. Por ejemplo, usemetadata_encryption=:wrappedkey_v0
. Para obtener más detalles, consulte la documentación de cifrado de metadatos .