Sistema Android Keystore

O sistema Android Keystore permite que você armazene chaves criptográficas em um contêiner para dificultar a extração delas do dispositivo. Quando as chaves estiverem no keystore, elas poderão ser usadas para operações criptográficas, mas não será possível exportá-las. Além disso, o sistema de keystore permite restringir quando e como as chaves podem ser usadas. Ele pode, por exemplo, exigir autenticação do usuário ou restringir o uso apenas em determinados modos criptográficos. Consulte a seção Recursos de segurança para saber mais.

O sistema Keystore é usado pela API KeyChain, introduzida no Android 4.0 (nível 14 da API), e pelo recurso de provedor do Android Keystore, introduzido no Android 4.3 (nível 18 da API). Este documento explica quando e como usar o provedor Android Keystore.

Recursos de segurança

O sistema Android Keystore protege as chaves contra o uso não autorizado de duas maneiras. Primeiro, reduz o risco de uso não autorizado da chave de fora do dispositivo Android, impedindo a extração da chave dos processos do aplicativo e do dispositivo Android como um todo. Em segundo lugar, o sistema de keystore reduz o risco do uso não autorizado das chaves dentro do dispositivo Android, fazendo com que os apps especifiquem os usos autorizados e apliquem essas restrições fora dos processos dos apps.

Prevenção contra extração

As chaves armazenadas no Android Keystore são protegidas contra a extração por duas medidas de segurança:

  • As chaves nunca entram no processo de aplicativos. Quando um app faz operações criptográficas usando uma chave do Android Keystore, o texto simples, o texto criptografado e as mensagens a serem assinadas ou verificadas são enviados, em segundo plano, a um processo de sistema que realiza as operações criptográficas. Se o processo do app for comprometido, o invasor poderá usar as chaves do app, mas não extraí-las (por exemplo, para uso fora do dispositivo Android).
  • A chave pode ser vinculada ao hardware seguro do dispositivo Android, como o ambiente de execução confiável (TEE) ou o Elemento de segurança (SE, na sigla em inglês). Quando esse recurso é ativado para uma chave, ela nunca é exposta fora do hardware protegido. Se o SO Android for comprometido ou se um invasor conseguir ler o armazenamento interno do dispositivo, ele poderá usar as chaves do keystore do Android de qualquer app no dispositivo, mas não extraí-las dele. Esse recurso será ativado apenas se o hardware protegido do dispositivo oferecer suporte à combinação específica de algoritmo de chave, modos de bloqueio, esquemas de preenchimento e resumos com que a chave tem permissão para ser usada.

    Para verificar se o recurso está ativado para uma chave, acesse o elemento KeyInfo dela. A próxima etapa depende da versão do SDK de destino do app:

    • Se o app for direcionado ao Android 10 (nível 29 da API) ou versões mais recentes, inspecione o valor de retorno de getSecurityLevel(). Os valores de retorno correspondentes a KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT ou KeyProperties.SecurityLevelEnum.STRONGBOX indicam que a chave está em um hardware seguro.
    • Caso o app seja destinado ao Android 9 (nível 28 da API) ou versões anteriores, inspecione o valor de retorno booleano de KeyInfo.isInsideSecurityHardware().

Módulo de segurança de hardware

Os dispositivos com suporte que usam o Android 9 (nível 28 da API) ou versões mais recentes podem ter um SafeBox Keymaster, uma implementação do Keymaster ou da HAL Keymint que reside em um elemento de segurança semelhante a um módulo de segurança de hardware. Embora os módulos de segurança de hardware possam se referir a muitas implementações diferentes de armazenamento de chaves em que um comprometimento do kernel do Linux não pode revelá-los, como o TEE, o StrongBox se refere explicitamente a dispositivos como Elementos de segurança incorporados (eSE, na sigla em inglês) ou unidades de processamento seguro (iSE, na sigla em inglês) on-SoC.

O módulo contém:

  • A própria CPU.
  • Armazenamento seguro.
  • Um gerador de números aleatórios real.
  • Outros mecanismos para resistir a violações do pacote e sideload não autorizado de apps.
  • Um timer seguro.
  • Um pin de notificação de reinicialização (ou equivalente), como entrada/saída de uso geral (GPIO, na sigla em inglês).

Para disponibilizar implementações de baixo consumo de bateria do StrongBox, um subconjunto de algoritmos e tamanhos de chave têm suporte:

  • RSA 2048
  • AES 128 e 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (com suporte a tamanhos de chave entre 8 a 64 bytes)
  • Padrão de criptografia de dados (DES) triplo
  • APDUs de comprimento estendido
  • Atestado de chaves
  • Suporte da Alteração H para upgrade

Ao gerar ou importar chaves usando o KeyStore, você indica uma preferência para armazenar a chave no StrongBox Keymaster transmitindo true para o método setIsStrongBoxBacked().

O StrongBox é um pouco mais lento e tem limitações de recursos, ou seja, ele oferece suporte a menos operações simultâneas em comparação com o TEE. Porém, as garantias de segurança são melhores contra ataques físicos e de canal lateral. Se você quiser priorizar garantias de segurança melhores em relação à eficiência de recursos do app, recomendamos usar o StrongBox nos dispositivos em que ele estiver disponível. Sempre que o StrongBox não estiver disponível, seu app poderá usar o TEE para armazenar materiais de chaves.

Autorizações de uso de chaves

Para evitar o uso não autorizado de chaves no dispositivo, o Android Keystore permite que os apps especifiquem usos autorizados quando gerarem ou importarem as chaves. Depois que uma chave é gerada ou importada, as autorizações não podem ser modificadas. Dessa forma, as autorizações são aplicadas pelo Android Keystore sempre que a chave é usada. Esse é um recurso de segurança avançado que geralmente é útil apenas se você precisa que um comprometimento do processo do seu aplicativo após uma geração/importação de chave (mas não antes ou durante) não leve a usos não autorizados da chave.

As autorizações de uso de chaves com suporte se encaixam nestas categorias:

  • Criptografia: a chave só pode ser usada com algoritmos, operações ou finalidades de chave autorizados (criptografar, descriptografar, assinar, verificar), esquemas de preenchimento, modos de bloqueio ou resumos.
  • Intervalo de validade temporal: a chave tem autorização para uso somente durante um intervalo de tempo definido.
  • Autenticação do usuário: a chave só pode ser usada se o usuário tiver sido autenticado recentemente. Consulte Exigir autenticação do usuário para o uso de chaves.

Como medida de segurança extra para chaves que estão dentro de um hardware protegido (consulte KeyInfo.isInsideSecurityHardware() ou, para apps direcionados ao Android 10 (nível 29 da API) ou versões mais recentes, KeyInfo.getSecurityLevel()), algumas autorizações de uso de chaves podem ser aplicadas pelo hardware seguro, dependendo do dispositivo Android. O hardware seguro normalmente aplica autorizações criptográficas e de autenticação do usuário. No entanto, o hardware seguro não costuma aplicar autorizações de intervalo de validade temporal porque normalmente não tem um relógio em tempo real seguro e independente.

É possível consultar se a autorização de autenticação do usuário de uma chave é aplicada pelo hardware seguro usando KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Escolher entre um conjunto de chaves ou o provedor Android Keystore

Use a API KeyChain quando quiser credenciais de sistema. Quando um app solicita o uso de alguma credencial com a API KeyChain, os usuários podem escolher, por uma interface fornecida pelo sistema, quais das credenciais instaladas um app pode acessar. Isso permite que vários apps usem o mesmo conjunto de credenciais com o consentimento do usuário.

Use o provedor Android Keystore para permitir que um app individual armazene as próprias credenciais, que só ele pode acessar. Isso permite que os apps gerenciem credenciais que só eles podem usar, com os mesmos benefícios de segurança que a API KeyChain fornece para credenciais de sistema. Esse método não exige que o usuário selecione as credenciais.

Usar o provedor Android Keystore

Para usar esse recurso, use as classes padrão KeyStore e KeyPairGenerator ou KeyGenerator com o provedor de AndroidKeyStore lançado no Android 4.3 (nível 18 da API).

O AndroidKeyStore é registrado como um tipo KeyStore para uso com o método KeyStore.getInstance(type) e como um provedor para uso com os métodos KeyPairGenerator.getInstance(algorithm, provider) e KeyGenerator.getInstance(algorithm, provider).

Como as operações criptográficas podem ser demoradas, os apps precisam evitar o uso de AndroidKeyStore na linha de execução principal para garantir que a interface do app continue responsiva. O StrictMode pode ajudar a encontrar lugares em que isso não acontece.

Gerar uma nova chave privada ou secreta

Para gerar um novo KeyPair contendo uma PrivateKey, especifique os atributos X.509 iniciais do certificado. Você pode usar KeyStore.setKeyEntry() para substituir o certificado posteriormente por um certificado assinado por uma autoridade certificadora (AC).

Para gerar o par de chaves, use um KeyPairGenerator com KeyGenParameterSpec.

Kotlin

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()

Java

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setDigests(KeyProperties.DIGEST_SHA256,
            KeyProperties.DIGEST_SHA512)
        .build());

KeyPair kp = kpg.generateKeyPair();

Importar chaves criptografadas para hardware seguro

O Android 9 (nível 28 da API) e versões mais recentes permitem que você importe chaves criptografadas com segurança para o Keystore usando um formato de chave codificado por ASN.1. Em seguida, o Keymaster descriptografa as chaves no Keystore para que o conteúdo delas nunca apareça como texto simples na memória do host do dispositivo. Esse processo oferece maior segurança de descriptografia de chave.

Para oferecer suporte à importação segura de chaves criptografadas para o Keystore, faça o seguinte:

  1. Gere um par de chaves que use a finalidade PURPOSE_WRAP_KEY. Recomendamos que você também adicione um atestado a esse par de chaves.

  2. Em um servidor ou máquina confiável, gere a mensagem ASN.1 para o SecureKeyWrapper.

    O wrapper tem este esquema:

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. Crie um objeto WrappedKeyEntry, transmitindo a mensagem ASN.1 como uma matriz de bytes.

  4. Transmita esse objeto WrappedKeyEntry para a sobrecarga de setEntry() que aceita um objeto Keystore.Entry.

Trabalhar com entradas de armazenamento de chaves

É possível acessar o provedor AndroidKeyStore usando todas as APIs KeyStore padrão.

Listar entradas

Liste entradas no Keystore chamando o método aliases():

Kotlin

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
   load(null)
}
val aliases: Enumeration<String> = ks.aliases()

Java

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();

Assinar e verificar os dados

Assine dados buscando KeyStore.Entry do armazenamento de chaves e usando as APIs de Signature, como sign():

Kotlin

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry: KeyStore.Entry = ks.getEntry(alias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return null
}
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
    initSign(entry.privateKey)
    update(data)
    sign()
}

Java

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return null;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();

Da mesma forma, verifique dados com o método verify(byte[]):

Kotlin

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
val ks = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
if (entry == null) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return false
}
val valid: Boolean = Signature.getInstance("SHA256withECDSA").run {
    initVerify(entry.certificate)
    update(data)
    verify(signature)
}

Java

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return false;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(((PrivateKeyEntry) entry).getCertificate());
s.update(data);
boolean valid = s.verify(signature);

Exigir autenticação do usuário para o uso de chaves

Ao gerar ou importar uma chave para o AndroidKeyStore, você pode especificar que a chave só poderá ser usada se o usuário tiver sido autenticado. O usuário é autenticado usando um subconjunto das credenciais seguras da tela de bloqueio (padrão/PIN/senha, credenciais biométricas).

Esse é um recurso de segurança avançado que geralmente é útil apenas se você precisar que um comprometimento do processo do seu aplicativo após uma geração/importação de chave (mas não antes ou durante) não possa ignorar a exigência de autenticação do usuário para usar a chave.

Quando uma chave só está autorizada a ser usada se o usuário tiver sido autenticado, você pode chamar setUserAuthenticationParameters() para configurá-la para operar em um destes modos:

Autorizar por um período
Todas as chaves poderão ser usadas assim que o usuário for autenticado usando uma das credenciais especificadas.
Autorizar pela duração de uma operação criptográfica específica

Cada operação que envolve uma chave específica precisa ser autorizada individualmente pelo usuário.

O app inicia esse processo chamando authenticate() em uma instância de BiometricPrompt.

Para cada chave criada, é possível optar por oferecer suporte a uma credencial biométrica forte, uma credencial de tela de bloqueio ou ambos os tipos de credenciais. Para determinar se o usuário configurou as credenciais necessárias para a chave do app, chame canAuthenticate().

Se uma chave aceitar apenas credenciais biométricas, ela será invalidada por padrão sempre que novos registros biométricos forem adicionados. É possível configurar a chave para que ela continue válida quando novos registros biométricos forem adicionados. Para fazer isso, transmita false para setInvalidatedByBiometricEnrollment().

Saiba mais sobre como adicionar recursos de autenticação biométrica ao app, incluindo como mostrar uma caixa de diálogo de autenticação biométrica.

Algoritmos com suporte

Artigos de blog

Consulte a postagem do blog Unificar o acesso ao armazenamento de chaves no ICS (link em inglês).