Chaves encapsuladas em hardware

Como a maioria dos softwares de criptografia de disco e arquivo, a criptografia de armazenamento do Android tradicionalmente depende da presença de chaves de criptografia brutas 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 de criptografia brutas.

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 se destina a proteger. No entanto, existe um desejo de fornecer maior proteção contra outros tipos de ataques, como ataques de inicialização a frio e ataques on-line, onde um invasor pode vazar a memória do sistema sem comprometer totalmente o dispositivo.

Para resolver esse problema, o Android 11 introduziu suporte para chaves encapsuladas por hardware , onde o suporte de hardware está presente. Chaves empacotadas por hardware são chaves de armazenamento conhecidas apenas na forma bruta por hardware dedicado; o software só vê e trabalha com essas chaves em formato encapsulado (criptografado). Este 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.

Nota : Um mecanismo de criptografia em linha (ou hardware de criptografia em linha ) refere-se ao hardware que criptografa/descriptografa dados enquanto eles estão a caminho de/para o dispositivo de armazenamento. Geralmente 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 encapsuladas por hardware, incluindo qual suporte de hardware é necessário para ele. Esta discussão se concentra na criptografia baseada em arquivos (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 slots de chave de um mecanismo de criptografia em linha. No entanto, esta abordagem apresenta alguns problemas:

  • O número de chaves de criptografia pode exceder o número de keylots.
  • Os mecanismos de criptografia embutidos só podem ser usados ​​para criptografar/descriptografar blocos completos de dados no disco. No entanto, no caso do FBE, o software ainda precisa ser capaz de realizar outros trabalhos criptográficos, como a criptografia de nomes de arquivos e a obtenção de identificadores de chave. O software ainda precisaria de acesso às chaves FBE brutas para realizar esse outro trabalho.

Para evitar esses problemas, as chaves de armazenamento são transformadas em chaves empacotadas por hardware , que só podem ser desembrulhadas 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 este 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 de chaves

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

O diagrama a seguir descreve uma hierarquia de chaves típica para FBE quando chaves encapsuladas 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 usuário Android específico. (No kernel, essa chave é chamada de chave mestra fscrypt .) A partir dessa chave, o kernel deriva as seguintes subchaves:

  • O identificador da chave. Não é usado para criptografia, mas sim um valor usado para identificar a chave com a qual um arquivo ou diretório específico é 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 chaves encapsuladas por hardware são usadas:

Hierarquia de chaves FBE (com chave encapsulada em hardware)
Figura 2. Hierarquia de chaves FBE (com chave encapsulada em 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 ao Linux para desbloquear um conjunto de diretórios criptografados. No entanto, agora essa chave está em formato efêmero e, para ser usada, ela deve ser passada para hardware dedicado. Este hardware deve implementar duas interfaces que usam uma chave encapsulada efêmera:

  • Uma interface para derivar inline_encryption_key e programá-la diretamente em um slot 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, esta 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, exceto criptografia de conteúdo de arquivo. Nos kernels comuns do Android, esta 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 bruta, o hardware deve usar um KDF criptograficamente forte. Este KDF deve seguir as melhores práticas de criptografia; deve ter uma força de segurança de pelo menos 256 bits, ou seja, 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 bruta já é uma chave uniformemente aleatória.

Tecnicamente, qualquer KDF que atenda aos requisitos de segurança poderá ser usado. Porém, para fins de teste, é necessário reimplementar o mesmo KDF no código de teste. Atualmente, um KDF foi revisto e implementado; ele pode ser encontrado no código-fonte de 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 e rótulos KDF para cada subchave.

Envolvimento de chave

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

  • Envolvimento efêmero : o hardware criptografa a chave bruta usando uma chave que é gerada aleatoriamente a cada inicialização e não é exposta diretamente fora do hardware.
  • Wrapping 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 ao kernel do Linux para desbloquear o armazenamento são empacotadas de forma efêmera. 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 uso fora do dispositivo, mesmo que extraídas no momento da inicialização. Por esta razão, é definido o conceito de embalagem de longo prazo.

Para suportar o 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 empacotado de longo prazo. Essas interfaces são acessadas indiretamente através do KeyMint e correspondem à tag TAG_STORAGE_KEY KeyMint. A capacidade "gerar" é usada pelo vold para gerar novas chaves de armazenamento para uso pelo Android, enquanto a capacidade "importar" é usada pelo 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 encapsulamento de chave é 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 encapsuladas em hardware. Isso inclui o suporte em componentes do espaço do usuário, como vold , bem como o suporte do kernel Linux em blk-crypto , fscrypt e dm-default-key .

No entanto, são necessárias algumas alterações específicas da implementação.

Mudanças no KeyMint

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

No Keymaster, exportKey foi usado em vez de convertStorageKeyToEphemeral .

Mudanças no kernel Linux

O driver do kernel Linux para o mecanismo de criptografia embutido do dispositivo deve ser modificado para dar suporte a chaves encapsuladas por hardware.

Para kernels android14 e superiores, defina BLK_CRYPTO_KEY_TYPE_HW_WRAPPED em blk_crypto_profile::key_types_supported , faça com que blk_crypto_ll_ops::keyslot_program e blk_crypto_ll_ops::keyslot_evict suportem programação/despejo de chaves empacotadas por hardware e implementem blk_crypto_ll_ops::derive_sw_secret .

Para kernels android12 e android13 , defina BLK_CRYPTO_FEATURE_WRAPPED_KEYS em blk_keyslot_manager::features , faça com que blk_ksm_ll_ops::keyslot_program e blk_ksm_ll_ops::keyslot_evict suportem programação/despejo de chaves empacotadas por hardware e implementem blk_ksm_ll_ops::derive_raw_secret .

Para kernels android11 , defina BLK_CRYPTO_FEATURE_WRAPPED_KEYS em keyslot_manager::features , faça com que keyslot_mgmt_ll_ops::keyslot_program e keyslot_mgmt_ll_ops::keyslot_evict suportem programação/despejo de chaves empacotadas por hardware e implementem keyslot_mgmt_ll_ops::derive_raw_secret .

Teste

Embora a criptografia com chaves empacotadas 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 encapsulada por hardware (por exemplo, FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy e DmDefaultKeyTest.TestHwWrappedKey ) não foram ignorados devido ao suporte para chaves encapsuladas por hardware não terem sido detectados, pois os resultados do teste ainda serão "aprovados" em Aquele caso.

Possibilitando

Depois que o suporte à 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 criptografia FBE e de metadados:

  • FBE: adicione o sinalizador wrappedkey_v0 ao parâmetro fileencryption . Por exemplo, use fileencryption=::inlinecrypt_optimized+wrappedkey_v0 . Para 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 sobre criptografia de metadados .