Como a maioria dos softwares de criptografia de disco e arquivos, a criptografia de armazenamento do Android tradicionalmente depende da presença das chaves de criptografia brutas na memória do sistema para que a criptografia possa ser realizada. Mesmo quando a criptografia é realizada por hardware dedicado em vez de 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 estão presentes durante um ataque off-line, que é o principal tipo de ataque contra o qual a criptografia de armazenamento foi projetada para proteger. No entanto, há o desejo de oferecer mais proteção contra outros tipos de ataques, como ataques de inicialização a frio e ataques on-line em que 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 em hardware, quando há compatibilidade de hardware. As chaves encapsuladas por hardware são chaves de armazenamento conhecidas na forma bruta apenas por hardware dedicado. O software vê e trabalha com essas chaves apenas na forma encapsulada (criptografada). Esse hardware precisa ser capaz de gerar e importar chaves de armazenamento, encapsular chaves de armazenamento de forma efêmera e de longo prazo, derivar subchaves, programar diretamente uma subchave em um mecanismo de criptografia inline e retornar uma subchave separada para o software.
Observação:um mecanismo de criptografia inline (ou hardware de criptografia inline) se refere a um hardware que criptografa/descriptografa dados enquanto eles estão a caminho do/para o dispositivo de armazenamento. Normalmente, esse é um controlador host UFS ou eMMC que implementa as extensões de criptografia definidas pela especificação JEDEC correspondente.
Design
Esta seção apresenta o design do recurso de chaves encapsuladas em hardware, incluindo o 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 das chaves de criptografia brutas na memória do sistema seria mantê-las apenas nos slots de chaves de um mecanismo de criptografia inline. No entanto, essa abordagem tem alguns problemas:
- O número de chaves de criptografia pode exceder o número de slots de chaves.
- Os mecanismos de criptografia inline geralmente perdem o conteúdo dos slots de chaves se o controlador do host de armazenamento for redefinido. A redefinição do controlador do host de armazenamento é um procedimento padrão de recuperação de erros que é executado se ocorrerem determinados tipos de erros de armazenamento, e esses erros podem ocorrer a qualquer momento. Portanto, quando a criptografia inline está sendo usada, o sistema operacional precisa estar sempre pronto para reprogramar os slots de chave sem intervenção do usuário.
- Os mecanismos de criptografia inline só podem ser usados para criptografar/descriptografar blocos completos de dados no disco. No entanto, no caso da FBE, o software ainda precisa ser capaz de realizar outros trabalhos de criptografia, como criptografia de nomes de arquivos e derivação de identificadores de chaves. 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 encapsuladas por hardware, que só podem ser desencapsuladas e usadas por hardware dedicado. Isso permite que um número ilimitado de chaves seja aceito. 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 inline.
Hierarquia de chaves
As chaves podem ser derivadas de outras chaves usando uma função de derivação de chaves (KDF), como HKDF, resultando em uma hierarquia de chaves.
O diagrama a seguir mostra uma hierarquia de chaves típica para FBE quando chaves encapsuladas por hardware não são usadas:
A chave de classe FBE é a chave de criptografia bruta que o Android transmite ao kernel do Linux para desbloquear um conjunto específico de diretórios criptografados, como o armazenamento criptografado por credenciais de um usuário específico do Android. No kernel, essa chave é chamada de chave principal do fscrypt. Com base nessa chave, o kernel deriva as seguintes subchaves:
- O identificador da chave. Ele não é usado para criptografia, mas sim um valor usado para identificar a chave com que 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
Em contraste, o diagrama a seguir mostra a hierarquia de chaves para FBE quando chaves encapsuladas por hardware são usadas:
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 transmite ao Linux para desbloquear um conjunto de diretórios criptografados. No entanto, agora essa chave está em formato encapsulado de maneira efêmera e, para ser usada, precisa ser transmitida a um hardware dedicado. Esse hardware precisa implementar duas interfaces que usam uma chave encapsulada de forma efêmera:
- Uma interface para derivar
inline_encryption_keye programá-lo diretamente em um slot de chave do mecanismo de criptografia inline. 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çãoblk_crypto_ll_ops::keyslot_program, que precisa ser implementada pelo driver de armazenamento. - Uma interface para derivar e retornar
sw_secret("software secret", antes chamado de "raw secret"), que é a chave usada pelo Linux para derivar as subchaves de tudo, exceto a criptografia de conteúdo de arquivos. Nos kernels comuns do Android, essa interface corresponde à operaçãoblk_crypto_ll_ops::derive_sw_secret, que precisa ser implementada pelo driver de armazenamento.
Para derivar inline_encryption_key e sw_secret da chave de armazenamento bruta, o hardware precisa usar uma KDF criptograficamente forte. Essa KDF precisa seguir as práticas recomendadas de criptografia e ter uma força de segurança de pelo menos 256 bits, o suficiente para qualquer algoritmo usado posteriormente. Ele também precisa usar um rótulo e um contexto distintos 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. Não é necessário fazer o alongamento de chave, porque a chave de armazenamento bruta 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, vts_kernel_encryption_test
implementa a mesma KDF no software para reproduzir o texto cifrado no disco
e verificar se ele está correto. Para facilitar os testes e garantir o uso de uma KDF segura e já revisada, recomendamos que o hardware implemente a KDF padrão que o teste verifica. Para hardware que usa um KDF diferente, consulte Testar chaves encapsuladas para saber como configurar o teste de acordo.
Encapsulamento de chaves
Para atender às metas de segurança das chaves encapsuladas por hardware, dois tipos de encapsulamento de chaves são definidos:
- Encapsulamento temporário: o hardware criptografa a chave bruta usando uma chave gerada aleatoriamente a cada inicialização e não é exposta diretamente fora do hardware.
- União de longo prazo: o hardware criptografa a chave bruta usando uma chave exclusiva e persistente integrada ao hardware que não é diretamente exposta fora dele.
Todas as chaves transmitidas ao kernel do Linux para desbloquear o armazenamento são encapsuladas de forma efêmera. Isso garante que, se um invasor conseguir extrair uma chave em uso da memória do sistema, ela não poderá ser usada não apenas fora do dispositivo, mas também nele após uma reinicialização.
Ao mesmo tempo, o Android ainda precisa armazenar uma versão criptografada das chaves no disco para que elas possam ser desbloqueadas. As chaves brutas funcionariam para essa finalidade. No entanto, é recomendável que as chaves brutas nunca estejam presentes na memória do sistema para que não possam ser extraídas para uso fora do dispositivo, mesmo que sejam extraídas na inicialização. Por isso, o conceito de encapsulamento de longo prazo é definido.
Para oferecer suporte ao gerenciamento de chaves encapsuladas dessas duas maneiras diferentes, o hardware precisa implementar as seguintes interfaces:
- Interfaces para gerar e importar chaves de armazenamento, retornando-as em
formato encapsulado de longo prazo. A interface generate é usada pelo
voldpara gerar novas chaves de armazenamento para uso pelo Android. A interface import é usada pelovts_kernel_encryption_testpara importar chaves de teste. - Uma interface para converter uma chave de armazenamento encapsulada de longo prazo em uma chave de armazenamento encapsulada efêmera. Essa interface é usada por
voldevts_kernel_encryption_testpara desbloquear o armazenamento.
O algoritmo de encapsulamento de chaves é um detalhe de implementação, mas precisa usar um AEAD forte, como AES-256-GCM com IVs aleatórios.
Mudanças necessárias no software
O AOSP já tem um framework básico para oferecer suporte a chaves encapsuladas em 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.
Mudanças no kernel do Linux
O driver do kernel do Linux para o controlador de armazenamento do dispositivo com suporte a criptografia inline precisa ser modificado para oferecer suporte a chaves encapsuladas em hardware.
Para kernels android17 e mais recentes:
- Defina
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDemblk_crypto_profile::key_types_supported. - Faça com que o
blk_crypto_ll_ops::keyslot_programofereça suporte à programação de chaves encapsuladas por hardware. - Fazer com que o
blk_crypto_ll_ops::keyslot_evictofereça suporte à remoção de chaves protegidas por hardware. - Implemente
blk_crypto_ll_ops::derive_sw_secret,blk_crypto_ll_ops::import_key,blk_crypto_ll_ops::generate_keyeblk_crypto_ll_ops::prepare_key.
Para kernels android14, android15 e android16:
- Defina
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDemblk_crypto_profile::key_types_supported. - Faça com que o
blk_crypto_ll_ops::keyslot_programofereça suporte à programação de chaves encapsuladas por hardware. - Fazer com que o
blk_crypto_ll_ops::keyslot_evictofereça suporte à remoção de chaves protegidas por hardware. - implementar
blk_crypto_ll_ops::derive_sw_secret;
Para kernels android12 e android13:
- Defina
BLK_CRYPTO_FEATURE_WRAPPED_KEYSemblk_keyslot_manager::features. - Faça com que o
blk_ksm_ll_ops::keyslot_programofereça suporte à programação de chaves encapsuladas por hardware. - Fazer com que o
blk_ksm_ll_ops::keyslot_evictofereça suporte à remoção de chaves protegidas por hardware. - implementar
blk_ksm_ll_ops::derive_raw_secret;
Para kernels android11:
- Defina
BLK_CRYPTO_FEATURE_WRAPPED_KEYSemkeyslot_manager::features. - Faça com que o
keyslot_mgmt_ll_ops::keyslot_programofereça suporte à programação de chaves encapsuladas por hardware. - Fazer com que o
keyslot_mgmt_ll_ops::keyslot_evictofereça suporte à remoção de chaves protegidas por hardware. - implementar
keyslot_mgmt_ll_ops::derive_raw_secret;
Mudanças no KeyMint (legado)
Na versão atual das chaves encapsuladas por hardware (wrappedkey), a
geração, importação e preparação de chaves encapsuladas por hardware usam os ioctls do kernel do Linux BLKCRYPTOGENERATEKEY,
BLKCRYPTOIMPORTKEY e BLKCRYPTOPREPAREKEY. Esses
ioctls correspondem a métodos em struct blk_crypto_ll_ops. O driver de armazenamento implementa esses métodos e se comunica com o hardware de encapsulamento de chaves para realizar a operação solicitada. Para mais informações sobre esses
ioctls, consulte a
documentação do kernel do Linux.
Esses ioctls foram adicionados no Linux 6.16. Em dispositivos que não foram lançados com a
solução baseada em ioctl, uma solução diferente usando o Android KeyMint (ou anteriormente
KeyMaster) é usada. A solução legada (wrappedkey_v0) não é compatível com o kernel principal do Linux nem com a solução atual. A solução
legada usa a seguinte funcionalidade do KeyMint:
- Suporte para
TAG_STORAGE_KEY, tanto para geração quanto para importação de chaves. - Compatibilidade com o método
convertStorageKeyToEphemeral.
Essa funcionalidade do KeyMint é necessária apenas em dispositivos que usam a solução
legada, correspondente a wrappedkey_v0 no arquivo fstab.
Os dispositivos que usam a solução atual, correspondente a wrappedkey
no arquivo fstab, não precisam implementar essa funcionalidade do KeyMint.
Testar chaves encapsuladas
Embora a criptografia com chaves encapsuladas por hardware seja mais difícil de testar do que a criptografia com chaves brutas, ainda é possível testar importando uma chave de teste e reimplementando a derivação de chave feita pelo hardware. Isso é implementado
em vts_kernel_encryption_test. Para executar esse teste,
use:
atest -v vts_kernel_encryption_test
Leia o registro 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 porque o suporte para chaves encapsuladas por hardware não foi detectado. Nesse caso, os resultados do teste ainda são "aprovados".
Por padrão, o vts_kernel_encryption_test pressupõe que o hardware implementa uma KDF chamada kdf1. Essa KDF pertence à família de KDFs do modo de contador do NIST SP 800-108 (em inglês) e usa o AES-256-CMAC como função pseudorrandomica. Para mais informações sobre CMAC, consulte a especificação
CMAC (em inglês). A KDF usa contextos e rótulos específicos ao derivar cada subchave. O hardware precisa implementar essa KDF, incluindo a escolha exata de contexto, rótulo e formatação da string de entrada fixa ao derivar cada subchave.
No entanto, o vts_kernel_encryption_test também implementa outras KDFs
kdf2 usando kdf4. Elas são tão seguras quanto kdf1 e diferem apenas na escolha de contextos, rótulos e formatação da string de entrada fixa. Eles existem apenas para acomodar diferentes
hardwares.
Para dispositivos que usam uma KDF diferente, defina a propriedade do sistema
ro.crypto.hw_wrapped_keys.kdf em
PRODUCT_VENDOR_PROPERTIES com o nome da KDF, conforme definido no
código-fonte de teste. Isso faz com que vts_kernel_encryption_test verifique
esse KDF em vez de kdf1. Por exemplo, para selecionar
kdf2, use:
PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2
Para dispositivos que usam uma KDF não compatível com o teste, adicione uma implementação dessa KDF ao teste e dê a ela um nome exclusivo.
Ativar chaves encapsuladas
Quando o suporte a chaves encapsuladas por hardware do dispositivo estiver funcionando corretamente, faça as seguintes mudanças no arquivo fstab do dispositivo para que o Android o use na criptografia de metadados e FBE:
- FBE: adicione a flag
wrappedkey(ouwrappedkey_v0para a versão legada) ao parâmetrofileencryption. Por exemplo, usefileencryption=::inlinecrypt_optimized+wrappedkey. Para mais detalhes, consulte a documentação do FBE. - Criptografia de metadados: adicione a flag
wrappedkey(ouwrappedkey_v0para a versão legada) ao parâmetrometadata_encryption. Por exemplo, usemetadata_encryption=:wrappedkey. Para mais detalhes, consulte a documentação sobre criptografia de metadados.
Em cada caso, há duas versões da flag:
- O
wrappedkey, compatível com o Android 17 e versões mais recentes, ativa a versão atual das chaves encapsuladas em hardware. Essa versão é compatível com o kernel principal do Linux. - O
wrappedkey_v0, compatível com o Android 11 e versões mais recentes, ativa a versão legada de chaves encapsuladas por hardware. Esta versão não é compatível com o kernel principal do Linux. Ele faz proxy de determinadas operações pelo KeyMint e usa um formato não padrão no disco. Para mais informações, consulte Mudanças no KeyMint (legado).
Em dispositivos lançados com o Android 17 ou versões mais recentes, prefira wrappedkey.
Em dispositivos que já foram lançados com o wrappedkey_v0, continue usando
wrappedkey_v0 para compatibilidade com versões anteriores.