Skip to content

MASTG-KNOW-0047: Armazenamento de Chaves Cryptographic

Para mitigar o uso não autorizado de chaves no dispositivo Android, o Android KeyStore permite que aplicativos especifiquem usos autorizados de suas chaves ao gerar ou importar as chaves. Uma vez definidas, as autorizações não podem ser alteradas.

Armazenamento de chaves — do mais seguro ao menos seguro:

  • a chave é armazenada no Android KeyStore com suporte de hardware
  • todas as chaves são armazenadas no servidor e ficam disponíveis após autenticação forte
  • a chave mestra é armazenada no servidor e usada para criptografar outras chaves, que são armazenadas no SharedPreferences do Android
  • a chave é derivada a cada uso a partir de uma senha fornecida pelo usuário com comprimento suficiente e salt
  • a chave é armazenada na implementação em software do Android KeyStore
  • a chave mestra é armazenada na implementação em software do Android KeyStore e usada para criptografar outras chaves, que são armazenadas no SharedPreferences
  • [não recomendado] todas as chaves são armazenadas no SharedPreferences
  • [não recomendado] chaves de criptografia embutidas no código-fonte (hardcoded)
  • [não recomendado] função ofuscação previsível ou função de derivação de chave baseada em atributos estáveis
  • [não recomendado] armazenar chaves geradas em locais públicos (como /sdcard/)

Armazenando chaves usando Android KeyStore com suporte de hardware

Você pode usar o Android KeyStore com suporte de hardware ( Android KeyStore) se o dispositivo estiver executando Android 7.0 (API level 24) ou superior com componente de hardware disponível (Trusted Execution Environment (TEE) ou Secure Element (SE)). Você pode até verificar se as chaves têm suporte de hardware usando as diretrizes fornecidas para a implementação segura de Atestado de Chave ( Key Attestation). Se um componente de hardware não estiver disponível e/ou for necessário suporte para Android 6.0 (API level 23) ou inferior, então você pode querer armazenar suas chaves em um servidor remoto e disponibilizá-las após autenticação.

Armazenando chaves no servidor

É possível armazenar chaves com segurança em um servidor de gerenciamento de chaves, porém o aplicativo precisa estar online para descriptografar os dados. Isso pode ser uma limitação para certos casos de uso de aplicativos móveis e deve ser cuidadosamente considerado, pois se torna parte da arquitetura do aplicativo e pode impactar significativamente a usabilidade.

Derivando chaves a partir de entrada do usuário

Derivar uma chave a partir de uma senha fornecida pelo usuário é uma solução comum (dependendo de qual nível de API Android você usa), mas também impacta a usabilidade, pode afetar a superfície de ataque e introduzir vulnerabilidades adicionais.

Cada vez que o aplicativo precisa realizar uma operação criptográfica, a senha do usuário é necessária. Ou o usuário é solicitado a fornecê-la toda vez, o que não é uma experiência ideal, ou a senha é mantida na memória enquanto o usuário estiver autenticado. Manter a senha na memória não é uma prática recomendada, pois qualquer material criptográfico deve ser mantido na memória apenas enquanto estiver em uso. Zerar uma chave é frequentemente uma tarefa muito desafiadora, conforme explicado em "Limpando material de chave")).

Além disso, considere que chaves derivadas de uma senha têm suas próprias fraquezas. Por exemplo, as senhas podem ser reutilizadas pelo usuário ou fáceis de adivinhar. Consulte o capítulo "Testando Criptografia" para mais informações.

Limpando material de chave

O material de chave deve ser limpo da memória assim que não for mais necessário. Existem certas limitações para limpar dados secretos de forma confiável em linguagens com coletor de lixo (Java) e strings imutáveis (Swift, Objective-C, Kotlin). O Guia de Referência da Arquitetura de Criptografia Java sugere usar char[] em vez de String para armazenar dados sensíveis, e anular o array após o uso.

Observe que alguns cifradores não limpam adequadamente seus arrays de bytes. Por exemplo, o Cifrador AES no BouncyCastle nem sempre limpa sua chave de trabalho mais recente, deixando algumas cópias do array de bytes na memória. Além disso, chaves baseadas em BigInteger (por exemplo, chaves privadas) não podem ser removidas do heap, nem zeradas sem esforço adicional. Limpar arrays de bytes pode ser alcançado escrevendo um wrapper que implementa Destroyable.

Armazenando chaves usando a API Android KeyStore

Uma maneira mais amigável e recomendada é usar o sistema API Android KeyStore (diretamente ou através do KeyChain) para armazenar material de chave. Se possível, o armazenamento com suporte de hardware deve ser usado. Caso contrário, deve-se recorrer à implementação em software do Android KeyStore. No entanto, esteja ciente de que a API AndroidKeyStore foi alterada significativamente ao longo das versões do Android. Em versões anteriores, a API AndroidKeyStore suportava apenas o armazenamento de pares de chaves pública/privada (por exemplo, RSA). O suporte a chaves simétricas foi adicionado apenas a partir do Android 6.0 (API level 23). Como resultado, um desenvolvedor precisa lidar com os diferentes níveis de API Android para armazenar chaves simétricas com segurança.

Armazenando chaves criptografando-as com outras chaves

Para armazenar chaves simétricas com segurança em dispositivos que executam Android 5.1 (API level 22) ou inferior, precisamos gerar um par de chaves pública/privada. Criptografamos a chave simétrica usando a chave pública e armazenamos a chave privada no AndroidKeyStore. A chave simétrica criptografada pode ser codificada usando base64 e armazenada no SharedPreferences. Sempre que precisarmos da chave simétrica, o aplicativo recupera a chave privada do AndroidKeyStore e descriptografa a chave simétrica.

Criptografia de envelope, ou encapsulamento de chave, é uma abordagem similar que usa criptografia simétrica para encapsular material de chave. Chaves de criptografia de dados (DEKs) podem ser criptografadas com chaves de criptografia de chaves (KEKs) que são armazenadas com segurança. DEKs criptografadas podem ser armazenadas no SharedPreferences ou gravadas em arquivos. Quando necessário, o aplicativo lê a KEK e então descriptografa a DEK. Consulte a OWASP Cryptographic Storage Cheat Sheet para aprender mais sobre criptografia de chaves criptográficas.

Além disso, como ilustração dessa abordagem, consulte EncryptedSharedPreferences da biblioteca de criptografia de segurança do Jetpack.

Aviso

A biblioteca de criptografia de segurança do Jetpack, incluindo as classes EncryptedFile e EncryptedSharedPreferences, foi descontinuada. No entanto, como um substituto oficial ainda não foi lançado, recomendamos usar essas classes até que uma esteja disponível.

Opções inseguras para armazenar chaves

Uma maneira menos segura de armazenar chaves de criptografia é no SharedPreferences do Android. Quando SharedPreferences são usados, o arquivo é legível apenas pelo aplicativo que o criou. No entanto, em dispositivos com root, qualquer outro aplicativo com acesso root pode ler o arquivo SharedPreferences de outros aplicativos. Este não é o caso do AndroidKeyStore, já que o acesso ao AndroidKeyStore é gerenciado no nível do kernel, o que requer consideravelmente mais trabalho e habilidade para contornar sem que o AndroidKeyStore limpe ou destrua as chaves.

As últimas três opções são usar chaves de criptografia embutidas no código-fonte, ter uma função de ofuscação previsível ou função de derivação de chave baseada em atributos estáveis, e armazenar chaves geradas em locais públicos como /sdcard/. Chaves de criptografia embutidas são um problema, pois isso significa que todas as instâncias do aplicativo usam a mesma chave de criptografia. Um atacante pode fazer engenharia reversa de uma cópia local do aplicativo para extrair a chave criptográfica e usar essa chave para descriptografar qualquer dado que foi criptografado pelo aplicativo em qualquer dispositivo.

Em seguida, quando você tem uma função de derivação de chave previsível baseada em identificadores que são acessíveis a outros aplicativos, o atacante só precisa encontrar a KDF e aplicá-la ao dispositivo para encontrar a chave. Por último, armazenar chaves de criptografia publicamente também é altamente desencorajado, pois outros aplicativos podem ter permissão para ler a partição pública e roubar as chaves.

Criptografia de dados usando bibliotecas de terceiros

Existem várias bibliotecas de código aberto diferentes que oferecem capacidades de criptografia específicas para a plataforma Android.

  • Java AES Crypto - Uma classe Android simples para criptografar e descriptografar strings.
  • SQL Cipher - SQLCipher é uma extensão de código aberto para SQLite que fornece criptografia transparente de 256-bit AES de arquivos de banco de dados.
  • Themis - Uma biblioteca criptográfica de alto nível multiplataforma que fornece a mesma API em várias plataformas, para proteger dados durante autenticação, armazenamento, mensagens, etc.

Por favor, tenha em mente que, desde que a chave não esteja armazenada no KeyStore, é sempre possível recuperar facilmente a chave em um dispositivo com root e então descriptografar os valores que você está tentando proteger.