Sistema Android Keystore

El sistema Android Keystore te permite almacenar claves criptográficas en un contenedor para que resulte más difícil extraerlas del dispositivo. Una vez que las claves se encuentran en el almacén de claves, puedes usarlas para operaciones criptográficas y el material de claves restante no se puede exportar. Además, el sistema Keystore te permite restringir cuándo y cómo se pueden usar las claves. Por ejemplo, cuando se solicita la autenticación del usuario para el uso de la clave o se restringen las claves para usarlas solo en ciertos modos criptográficos. Para obtener más información, consulta la sección Funciones de seguridad.

La API de KeyChain, que se introdujo en Android 4.0 (API nivel 14), y la función del proveedor de Android Keystore, que se introdujo en Android 4.3 (API nivel 18), usan el sistema Keystore. En este documento, se explica cómo y cuándo debe usarse el proveedor del sistema Android Keystore.

Funciones de seguridad

El sistema Android Keystore protege el material de claves del uso no autorizado de dos maneras. Primero, reduce el riesgo de uso no autorizado del material de claves desde el exterior del dispositivo Android, ya que evita la extracción del material de claves de los procesos de la aplicación y del dispositivo Android como un todo. En segundo lugar, el sistema Keystore reduce el riesgo de uso no autorizado de material de claves dentro del dispositivo Android. Para ello, hace que las apps especifiquen los usos autorizados de sus claves y, luego, aplica esas restricciones en el exterior de los procesos de las apps.

Prevención de extracción

El material de claves de las claves de Android Keystore está protegido contra la extracción con dos medidas de seguridad:

  • El material de claves nunca ingresa al proceso de la aplicación. Cuando una aplicación realiza operaciones criptográficas usando una clave de Android Keystore, el texto simple, el texto cifrado y los mensajes en segundo plano que se firmarán o verificarán se envían a un proceso del sistema que se ocupa de las operaciones criptográficas. Si el proceso de la app está comprometido, el atacante podría usar las claves de la app, pero no podría extraer su material de claves (por ejemplo, para usarlo fuera del dispositivo Android).
  • El material de claves se puede vincular al hardware seguro del dispositivo Android, como el entorno de ejecución confiable (TEE) o el elemento seguro (SE). Cuando se habilita esta función para una clave, su material de clave nunca se expone fuera del hardware seguro. Si el SO Android está comprometido o un atacante puede leer el almacenamiento interno del dispositivo, es posible que pueda usar las claves de Android Keystore de cualquier app en el dispositivo Android, pero no podrá extraerlas del dispositivo. Esta función se habilita únicamente si el hardware seguro del dispositivo admite la combinación específica de algoritmo de la clave, modos de bloqueo, esquemas de relleno y resúmenes con la que se autoriza el uso de la clave.

    Si quieres verificar si la función está habilitada para una clave, obtén un KeyInfo para ella. El siguiente paso depende de la versión del SDK de destino de tu app:

    • Si tu app se orienta a Android 10 (nivel de API 29) o versiones posteriores, inspecciona el valor que se devuelve de getSecurityLevel(). Los valores que se devuelven coinciden con KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT o KeyProperties.SecurityLevelEnum.STRONGBOX, indican que la clave reside en hardware seguro.
    • Si tu app se orienta a Android 9 (nivel de API 28) o versiones anteriores, inspecciona el valor booleano que se devuelve de KeyInfo.isInsideSecurityHardware().

Módulo de seguridad de hardware

Los dispositivos compatibles que ejecutan Android 9 (nivel de API 28) o versiones posteriores pueden tener un StrongBox Keymaster, una implementación de la HAL de Keymaster o Keymint que reside en un elemento seguro similar a un módulo de seguridad de hardware. Si bien los módulos de seguridad de hardware pueden hacer referencia a muchas implementaciones diferentes de almacenamiento de claves donde un compromiso del kernel de Linux no puede revelarlos, como TEE, StrongBox se refiere explícitamente a dispositivos como elementos seguros incorporados (eSE) o unidades de procesamiento seguro (iSE) en SoC.

El módulo contiene lo siguiente:

  • Su propia CPU
  • Almacenamiento seguro
  • Un generador de números aleatorios reales
  • Mecanismos adicionales de resistencia contra alteraciones de paquetes y sideloading no autorizado de apps
  • Un temporizador seguro
  • Un PIN de notificación de reinicio (o equivalente), como entrada y salida de uso general (GPIO)

Para admitir implementaciones de StrongBox de bajo consumo, se admite un subconjunto de algoritmos y tamaños de clave:

  • RSA 2048
  • AES 128 y 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (admite tamaños de clave de 8 a 64 bytes)
  • Triple DES
  • APDU de extensión prolongada
  • Certificación de claves
  • Compatibilidad de la enmienda H para la actualización

Cuando generas o importas claves con la clase KeyStore, indicas una preferencia por almacenar la clave en el StrongBox Keymaster pasando true al método setIsStrongBoxBacked().

Aunque StrongBox es un poco más lento y tiene recursos limitados (lo que significa que admite menos operaciones simultáneas) en comparación con TEE, StrongBox proporciona mejores garantías de seguridad contra ataques físicos y de canal lateral. Si deseas priorizar garantías de seguridad más altas sobre la eficiencia de recursos de la app, te recomendamos que uses StrongBox en los dispositivos en los que está disponible. Siempre que StrongBox no esté disponible, tu app puede recurrir a TEE para almacenar materiales clave.

Autorizaciones de uso de las claves

Para evitar el uso no autorizado de claves en el dispositivo Android, Android Keystore permite que las apps especifiquen usos autorizados de sus claves cuando las generan o las importan. Una vez que se genera o importa una clave, no se pueden cambiar sus autorizaciones. Luego, Android Keystore aplica las autorizaciones cada vez que se usa la clave. Esta es una función de seguridad avanzada que generalmente es útil solo si tus requisitos establecen que el compromiso del proceso de tu aplicación después de la generación o importación de la clave (pero no antes de esta ni durante su transcurso) no pueda permitir el uso no autorizado de la clave.

Las autorizaciones de uso de claves compatibles pertenecen a las siguientes categorías:

  • Criptografía: La clave solo se puede usar con algoritmos, operaciones o propósitos de clave autorizados (encriptar, desencriptar, firmar y verificar), esquemas de relleno, modos de bloqueo o resúmenes.
  • Intervalo de validez temporal: La clave está autorizada para usarla solo durante un intervalo de tiempo definido.
  • Autenticación del usuario: La clave solo se puede usar si se autenticó al usuario en un período lo suficientemente reciente. Consulta la sección Solicita la autenticación del usuario para el uso de la clave.

Como medida de seguridad adicional para las claves cuyo material de claves se encuentra dentro de hardware seguro (consulta KeyInfo.isInsideSecurityHardware() o, en el caso de las apps orientadas a Android 10 [nivel de API 29] o versiones posteriores, KeyInfo.getSecurityLevel()), el hardware seguro podría aplicar algunas autorizaciones de uso de claves, según el dispositivo Android. El hardware seguro normalmente aplica autorizaciones criptográficas y de autenticación de usuarios. Sin embargo, el hardware seguro no suele aplicar autorizaciones de intervalo de validez temporal porque, por lo general, no tiene un reloj en tiempo real independiente y seguro.

Puedes consultar si el hardware seguro aplica la autorización de autenticación del usuario de una clave a través de KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Elige entre Keychain o el proveedor de Android Keystore

Usa la API de KeyChain cuando quieras credenciales para todo el sistema. Cuando una app solicita el uso de cualquier credencial a través de la API de KeyChain, los usuarios pueden elegir, con una IU proporcionada por el sistema, la credencial instalada a la que puede acceder una app. Esto permite que varias apps usen el mismo conjunto de credenciales con el consentimiento del usuario.

Usa el proveedor de Android Keystore para permitir que una app individual almacene sus propias credenciales, a las que solo puede acceder esa app. Esto permite que las apps administren credenciales que solo ellas pueden usar y proporciona los mismos beneficios de seguridad que ofrece la API de KeyChain para las credenciales en todo el sistema. Este método no requiere que el usuario seleccione las credenciales.

Cómo usar el proveedor de Android Keystore

Para usar esta función, se emplean las clases KeyStore, KeyPairGenerator o KeyGenerator estándares junto con el proveedor de AndroidKeyStore incluido en Android 4.3 (nivel de API 18).

AndroidKeyStore está registrado como un tipo de KeyStore cuando se lo usa con el método KeyStore.getInstance(type) y como proveedor cuando se lo usa con los métodos KeyPairGenerator.getInstance(algorithm, provider) y KeyGenerator.getInstance(algorithm, provider).

Genera una nueva clave privada o secreta

Para generar un KeyPair nuevo que contenga una PrivateKey, debes especificar los atributos iniciales X.509 del certificado. Puedes usar KeyStore.setKeyEntry() para reemplazar el certificado posteriormente por un certificado firmado por una autoridad certificadora (AC).

Para generar el par de claves, usa un KeyPairGenerator con 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();

Importa claves encriptadas a hardware seguro

Android 9 (nivel de API 28) y versiones posteriores permiten importar claves encriptadas de forma segura al almacén de claves a través de un formato de clave con codificación ASN.1. El Keymaster luego desencripta las claves en el almacén de claves para que el contenido de estas nunca aparezca como texto simple en la memoria host del dispositivo. Este proceso proporciona seguridad adicional en la desencriptación de claves.

Para admitir la importación segura de claves encriptadas al almacén de claves, completa los siguientes pasos:

  1. Genera un par de claves que use el propósito PURPOSE_WRAP_KEY. Te recomendamos que también agregues una certificación a este par de claves.

  2. En un servidor o máquina que te resulte confiable, genera el mensaje ASN.1 para el elemento SecureKeyWrapper.

    El contenedor incluye el siguiente 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. Crea un objeto WrappedKeyEntry y pasa el mensaje ASN.1 como un arreglo de bytes.

  4. Pasa este objeto WrappedKeyEntry a la sobrecarga de setEntry() que acepta un objeto Keystore.Entry.

Trabaja con entradas del almacén de claves

Puedes acceder al proveedor de AndroidKeyStore a través de todas las APIs de KeyStore estándar.

Enumera las entradas

Para enumerar entradas en el almacén de claves, llama al 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();

Firma y verifica los datos

Obtén la KeyStore.Entry del almacén de claves y usa las APIs de Signature, como sign(), para firmar datos:

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();

También puedes verificar datos con el 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);

Solicita la autenticación del usuario para el uso de la clave

Al generar una clave o importarla a AndroidKeyStore, puedes especificar que solo se autorice el uso de la clave si se autenticó el usuario. El usuario se autentica a través de un subconjunto de las credenciales seguras de la pantalla de bloqueo (patrón, PIN o contraseña, credenciales biométricas).

Esta es una función de seguridad avanzada que generalmente es útil solo si tus requisitos indican que el compromiso del proceso de tu aplicación después de la generación o importación de la clave (pero no antes de esta ni durante su transcurso) no puede omitir el requisito por el cual se debe autenticar el usuario para que este pueda usar la clave.

Cuando se autoriza el uso de una clave solo si se autenticó al usuario, puedes llamar a setUserAuthenticationParameters() para configurarla de manera tal que funcione en uno de los siguientes modos:

Autorizar durante un tiempo
El uso de todas las claves se autoriza en cuanto el usuario se autentica con una de las credenciales especificadas.
Autorizar mientras dure una operación criptográfica específica

El usuario debe autorizar de forma individual cada operación que involucre una clave específica.

Tu app inicia este proceso llamando a authenticate() en una instancia de BiometricPrompt.

Por cada clave que crees, puedes optar por admitir una credencial biométrica segura, una credencial de pantalla de bloqueo o ambas. Para determinar si el usuario configuró las credenciales que requieren la clave de tu app, llama a canAuthenticate().

Si una clave solo admite credenciales biométricas, se invalida de forma predeterminada cada vez que se agregan inscripciones biométricas nuevas. Puedes configurar la clave para que siga siendo válida cuando se agreguen nuevas inscripciones biométricas. Para hacerlo, pasa false a setInvalidatedByBiometricEnrollment().

Obtén más información para agregar capacidades de autenticación biométrica a tu app, lo que incluye cómo mostrar un diálogo de autenticación biométrica.

Algoritmos compatibles

Artículos del blog

Consulta la entrada de blog Cómo unificar el acceso al almacén de claves en ICS.