Chaves encapsuladas em hardware

Como a maioria dos softwares de criptografia de disco e arquivo, a criptografia de armazenamento do Android tradicionalmente depende das chaves de criptografia brutas presentes na memória do sistema para que a criptografia possa ser executada. Mesmo quando a criptografia é realizada por hardware dedicado e não por software, o software geralmente ainda precisa gerenciar as chaves brutas de criptografia.

Tradicionalmente, isso não é visto como um problema porque as chaves não estarão presentes durante um ataque offline, que é o principal tipo de ataque contra o qual a criptografia de armazenamento visa proteger. No entanto, existe o desejo de fornecer maior proteção contra outros tipos de ataques, como ataques de inicialização a frio e ataques online, nos quais um invasor pode vazar a memória do sistema sem comprometer totalmente o dispositivo.

Para resolver esse problema, o Android 11 introduziu o suporte para chaves agrupadas por hardware , onde o suporte de hardware está presente. As chaves encapsuladas em hardware são chaves de armazenamento conhecidas apenas na forma bruta pelo hardware dedicado; o software apenas vê e trabalha com essas chaves na forma encapsulada (criptografada). Esse hardware deve ser capaz de gerar e importar chaves de armazenamento, agrupar chaves de armazenamento em formas efêmeras e de longo prazo, derivar subchaves, programar diretamente uma subchave em um mecanismo de criptografia em linha e retornar uma subchave separada ao software.

Observação : um mecanismo de criptografia em linha (ou hardware de criptografia em linha ) refere-se ao hardware que criptografa/descriptografa dados enquanto está a caminho de/para o dispositivo de armazenamento. Normalmente, este é um controlador de host UFS ou eMMC que implementa as extensões criptográficas definidas pela especificação JEDEC correspondente.

Projeto

Esta seção apresenta o design do recurso de chaves agrupadas por hardware, incluindo qual suporte de hardware é necessário para isso. Esta discussão se concentra na criptografia baseada em arquivo (FBE), mas a solução também se aplica à criptografia de metadados .

Uma maneira de evitar a necessidade de chaves de criptografia brutas na memória do sistema seria mantê-las apenas nos compartimentos de chave de um mecanismo de criptografia em linha. No entanto, esta abordagem esbarra em alguns problemas:

  • O número de chaves de criptografia pode exceder o número de slots de chaves.
  • Mecanismos de criptografia em linha só podem ser usados ​​para criptografar/descriptografar blocos completos de dados em disco. No entanto, no caso do FBE, o software ainda precisa ser capaz de fazer outro trabalho criptográfico, como criptografia de nomes de arquivos e derivação de identificadores de chave. O software ainda precisaria de acesso às chaves brutas do FBE para fazer esse outro trabalho.

Para evitar esses problemas, as chaves de armazenamento são transformadas em chaves encapsuladas em hardware , que só podem ser desencapsuladas e usadas por hardware dedicado. Isso permite que um número ilimitado de chaves seja suportado. Além disso, a hierarquia de chaves é modificada e parcialmente movida para esse hardware, o que permite que uma subchave seja retornada ao software para tarefas que não podem usar um mecanismo de criptografia em linha.

Hierarquia chave

As chaves podem ser derivadas de outras chaves usando um KDF (função de derivação de chave), como HKDF , resultando em uma hierarquia de chave .

O diagrama a seguir descreve uma hierarquia de chave típica para FBE quando as chaves agrupadas por hardware não são usadas:

Hierarquia de chaves FBE (padrão)
Figura 1. Hierarquia de chaves FBE (padrão)

A chave de classe FBE é a chave de criptografia bruta que o Android passa para o kernel do Linux para desbloquear um conjunto específico de diretórios criptografados, como o armazenamento criptografado por credencial para um determinado usuário do Android. (No kernel, essa chave é chamada de chave mestra fscrypt .) A partir dessa chave, o kernel deriva as seguintes subchaves:

  • O identificador de chave. Isso não é usado para criptografia, mas é um valor usado para identificar a chave com a qual um determinado arquivo ou diretório é protegido.
  • A chave de criptografia do conteúdo do arquivo
  • A chave de criptografia de nomes de arquivos

Por outro lado, o diagrama a seguir descreve a hierarquia de chaves para FBE quando as chaves agrupadas por hardware são usadas:

Hierarquia de chaves FBE (com chave encapsulada por hardware)
Figura 2. Hierarquia de chaves FBE (com chave encapsulada por hardware)

Em comparação com o caso anterior, um nível adicional foi adicionado à hierarquia de chaves e a chave de criptografia do conteúdo do arquivo foi realocada. O nó raiz ainda representa a chave que o Android passa para o Linux para desbloquear um conjunto de diretórios criptografados. No entanto, agora essa chave está em forma efêmera e, para ser usada, deve ser passada para hardware dedicado. Este hardware deve implementar duas interfaces que recebem uma chave efêmeramente agrupada:

  • Uma interface para derivar inline_encryption_key e programá-la diretamente em um compartimento de chave do mecanismo de criptografia em linha. Isso permite que o conteúdo do arquivo seja criptografado/descriptografado sem que o software tenha acesso à chave bruta. Nos kernels comuns do Android, essa interface corresponde à operação blk_crypto_ll_ops::keyslot_program , que deve ser implementada pelo driver de armazenamento.
  • Uma interface para derivar e retornar sw_secret ("segredo de software" -- também chamado de "segredo bruto" em alguns lugares), que é a chave que o Linux usa para derivar as subchaves para tudo que não seja a criptografia do conteúdo do arquivo. Nos kernels comuns do Android, essa interface corresponde à operação blk_crypto_ll_ops::derive_sw_secret , que deve ser implementada pelo driver de armazenamento.

Para derivar inline_encryption_key e sw_secret da chave de armazenamento bruto, o hardware deve usar um KDF criptograficamente forte. Este KDF deve seguir as melhores práticas de criptografia; ele deve ter uma força de segurança de pelo menos 256 bits, ou seja, o suficiente para qualquer algoritmo usado posteriormente. Ele também deve usar um rótulo distinto, contexto e/ou sequência de informações específicas do aplicativo ao derivar cada tipo de subchave para garantir que as subchaves resultantes sejam isoladas criptograficamente, ou seja, o conhecimento de uma delas não revela nenhuma outra. O alongamento de chave não é necessário, pois a chave de armazenamento bruto já é uma chave aleatória uniforme.

Tecnicamente, qualquer KDF que atenda aos requisitos de segurança pode ser usado. No entanto, para fins de teste, é necessário reimplementar o mesmo KDF no código de teste. Atualmente, um KDF foi revisado e implementado; ele pode ser encontrado no código-fonte para vts_kernel_encryption_test . Recomenda-se que o hardware use este KDF, que usa NIST SP 800-108 "KDF in Counter Mode" com AES-256-CMAC como PRF. Observe que, para ser compatível, todas as partes do algoritmo devem ser idênticas, incluindo a escolha de contextos KDF e rótulos para cada subchave.

Envolvimento de chave

Para atender aos objetivos de segurança das chaves encapsuladas por hardware, dois tipos de encapsulamento de chave são definidos:

  • Embalagem efêmera : o hardware criptografa a chave bruta usando uma chave que é gerada aleatoriamente a cada inicialização e não é exposta diretamente fora do hardware.
  • Empacotamento de longo prazo : o hardware criptografa a chave bruta usando uma chave exclusiva e persistente incorporada ao hardware que não é exposta diretamente fora do hardware.

Todas as chaves passadas para o kernel do Linux para desbloquear o armazenamento são encapsuladas efêmeramente. Isso garante que, se um invasor conseguir extrair uma chave em uso da memória do sistema, essa chave ficará inutilizável não apenas fora do dispositivo, mas também no dispositivo após uma reinicialização.

Ao mesmo tempo, o Android ainda precisa ser capaz de armazenar uma versão criptografada das chaves no disco para que possam ser desbloqueadas em primeiro lugar. As chaves brutas funcionariam para esse propósito. No entanto, é desejável que as chaves brutas nunca estejam presentes na memória do sistema, para que elas nunca possam ser extraídas para serem usadas fora do dispositivo, mesmo se extraídas no momento da inicialização. Por esta razão, o conceito de embalagem de longo prazo é definido.

Para oferecer suporte ao gerenciamento de chaves agrupadas dessas duas maneiras diferentes, o hardware deve implementar as seguintes interfaces:

  • Interfaces para gerar e importar chaves de armazenamento, retornando-as em formato encapsulado de longo prazo. Essas interfaces são acessadas indiretamente por meio do KeyMint e correspondem à tag TAG_STORAGE_KEY KeyMint. A capacidade "gerar" é usada por vold para gerar novas chaves de armazenamento para uso pelo Android, enquanto a capacidade "importar" é usada por vts_kernel_encryption_test para importar chaves de teste.
  • Uma interface para converter uma chave de armazenamento encapsulada de longo prazo em uma chave de armazenamento encapsulada efêmera. Isso corresponde ao método convertStorageKeyToEphemeral KeyMint. Este método é usado por vold e vts_kernel_encryption_test para desbloquear o armazenamento.

O algoritmo de agrupamento de chaves é um detalhe de implementação, mas deve usar um AEAD forte, como AES-256-GCM com IVs aleatórios.

Mudanças de software necessárias

O AOSP já possui uma estrutura básica para suportar chaves agrupadas por hardware. Isso inclui o suporte em componentes do espaço do usuário, como vold , bem como o suporte do kernel do Linux em blk-crypto , fscrypt e dm-default-key .

No entanto, algumas mudanças específicas de implementação são necessárias.

Mudanças KeyMint

A implementação KeyMint do dispositivo deve ser modificada para dar suporte TAG_STORAGE_KEY e implementar o método convertStorageKeyToEphemeral .

No Keymaster, exportKey foi usado em vez de convertStorageKeyToEphemeral .

Mudanças no kernel do Linux

O driver do kernel do Linux para o mecanismo de criptografia em linha do dispositivo deve ser modificado para oferecer suporte a chaves agrupadas por hardware.

Para kernels android14 e superiores, defina BLK_CRYPTO_KEY_TYPE_HW_WRAPPED em blk_crypto_profile::key_types_supported , faça blk_crypto_ll_ops::keyslot_program e blk_crypto_ll_ops::keyslot_evict suporta programação/remoção de chaves encapsuladas por hardware e implemente blk_crypto_ll_ops::derive_sw_secret .

Para os kernels android12 e android13 , defina BLK_CRYPTO_FEATURE_WRAPPED_KEYS em blk_keyslot_manager::features , faça blk_ksm_ll_ops::keyslot_program e blk_ksm_ll_ops::keyslot_evict suporta programação/remoção de chaves encapsuladas por hardware e implemente blk_ksm_ll_ops::derive_raw_secret .

Para kernels android11 , defina BLK_CRYPTO_FEATURE_WRAPPED_KEYS em keyslot_manager::features , faça keyslot_mgmt_ll_ops::keyslot_program e keyslot_mgmt_ll_ops::keyslot_evict suporta programação/remoção de chaves encapsuladas por hardware e implemente keyslot_mgmt_ll_ops::derive_raw_secret .

teste

Embora a criptografia com chaves agrupadas por hardware seja mais difícil de testar do que a criptografia com chaves padrão, ainda é possível testar importando uma chave de teste e reimplementando a derivação de chave que o hardware faz. Isso é implementado em vts_kernel_encryption_test . Para executar este teste, execute:

atest -v vts_kernel_encryption_test

Leia o log de teste e verifique se os casos de teste de chave agrupada por hardware (por exemplo, FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy e DmDefaultKeyTest.TestHwWrappedKey ) não foram ignorados devido ao suporte para chaves agrupadas por hardware não sendo detectadas, pois os resultados do teste ainda serão "aprovados" em Aquele caso.

Possibilitando

Depois que o suporte de chave encapsulada por hardware do dispositivo estiver funcionando corretamente, você poderá fazer as seguintes alterações no arquivo fstab do dispositivo para que o Android o use para FBE e criptografia de metadados:

  • FBE: adicione o sinalizador wrappedkey_v0 ao parâmetro fileencryption . Por exemplo, use fileencryption=::inlinecrypt_optimized+wrappedkey_v0 . Para obter mais detalhes, consulte a documentação do FBE .
  • Criptografia de metadados: adicione o sinalizador wrappedkey_v0 ao parâmetro metadata_encryption . Por exemplo, use metadata_encryption=:wrappedkey_v0 . Para obter mais detalhes, consulte a documentação de criptografia de metadados .