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, se pueden usar para realizar operaciones criptográficas con el material de claves restante no exportable. Además, mediante prestaciones permite restringir el momento y la manera en que se pueden usar las claves; por ejemplo, la solicitud de autenticación del usuario para el uso de claves o la restricción de estas para su uso exclusivo en determinados 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); la función del proveedor de Android Keystore, que se introdujo en Android 4.3 (API nivel  18); y la biblioteca de seguridad, disponible como parte de Jetpack, usan el sistema de almacén de claves. En este documento, se explica cómo y cuándo debe usarse el proveedor del almacén de claves d e Android.

Funciones de seguridad

El sistema Android Keystore protege el material de claves contra usos no autorizados. En primer lugar, evita la extracción del material de claves de los procesos de la aplicación y del dispositivo Android en su totalidad a fin de reducir el uso no autorizado de claves fuera del dispositivo. En segundo lugar, permite que las apps especifiquen usos autorizados de sus claves y aplica estas restricciones fuera de los procesos de las apps para reducir el uso no autorizado de material de claves en el dispositivo.

Prevención de extracción

El material de claves de las claves de Android Keystore está protegido contra la extracción mediante 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 sin formato, 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 puede usar las claves de la app. Sin embargo, no podrá extraer el material de claves (por ejemplo, para usarlo fuera del dispositivo Android).
  • El material de claves puede estar vinculado al hardware seguro (p. ej., entorno de ejecución seguro [TEE] y elemento seguro [SE]) del dispositivo Android. Cuando se habilita esta función para una clave, su material de clave nunca se expone fuera del hardware seguro. Si el SO Android se ve comprometido o un atacante puede leer el almacenamiento interno del dispositivo, este puede usar las claves de Android Keystore de cualquier app del dispositivo Android, pero no podrá extraerlas de este. 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 averiguar si la función está habilitada para una clave, obtén KeyInfo para la clave e inspecciona el valor de retorno de KeyInfo.isInsideSecurityHardware().

Módulo de seguridad de hardware

Los dispositivos compatibles que ejecutan Android 9 (nivel de API 28) o versiones posteriores pueden contar con un StrongBox Keymaster, una implementación del Keymaster HAL que reside en un módulo de seguridad de hardware. 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 e instalaciones de prueba no autorizadas de apps

Cuando se verifican las claves almacenadas en el StrongBox Keymaster, el sistema corrobora la integridad de una clave con el entorno de ejecución seguro (TEE).

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

  • RSA 2048
  • AES 128 y 256
  • ECDSA P-256
  • HMAC-SHA256 (admite tamaños de clave de 8 a 64 bytes)
  • Triple DES 168

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

Autorizaciones de uso de las claves

Para mitigar el uso no autorizado de claves en el dispositivo Android, Android Keystore permite que las apps especifiquen usos autorizados de sus claves al generarlas o importarlas. Una vez que se genera o se importa una clave, no se pueden cambiar sus autorizaciones. Luego, Android Keystore aplica las autorizaciones cada vez que se use 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: Corresponde a algoritmos, operaciones o propósitos de clave autorizados (encriptación, desencriptación, firma y verificación), esquemas de relleno, modos de bloqueo, resúmenes con los que se puede usar la clave.
  • Intervalo de validez temporal: Corresponde a un intervalo durante el cual está autorizado el uso de la clave.
  • 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, en el caso de las claves cuyo material de claves se encuentre dentro de hardware seguro (consulta KeyInfo.isInsideSecurityHardware()), se pueden aplicar algunas autorizaciones de uso de claves mediante hardware seguro, según el dispositivo Android. Es probable que las autorizaciones criptográfica y de autenticación del usuario se apliquen mediante el hardware seguro. Por el contrario, es poco probable que las autorizaciones de intervalo de validez temporal se apliquen mediante el hardware seguro, ya que normalmente éste no cuenta con un reloj independiente y seguro que funcione en tiempo real.

Puedes averiguar si la autorización de autenticación del usuario de una clave se aplica mediante el hardware seguro por medio del método KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Elige entre Keychain o el proveedor del almacén de claves de de Android

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, mediante 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 del almacén de claves de Android para permitir que una app individual almacene sus propias credenciales, a las cuales solo la app puede acceder. Esto permite que las apps administren credenciales que solo ellas pueden usar y entrega los mismos beneficios de seguridad que ofrece la API de KeyChain para las credenciales en todo el sistema. Este método no requiere la interacción del usuario para seleccionar las credenciales.

Usa el proveedor del almacén de claves de Android

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 (API nivel 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

Si quieres generar una clave PrivateKey nueva, también debes especificar los atributos iniciales X.509 que tendrá el certificado autofirmado.

La biblioteca de Seguridad proporciona una implementación predeterminada para generar una clave simétrica válida, como se muestra en el siguiente fragmento:

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
    

Java

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);
    

Puedes usar KeyStore.setKeyEntry para reemplazar el certificado más tarde con un certificado firmado por una autoridad certificada (CA).

Para generar la clave, usa un KeyPairGenerator con KeyPairGeneratorSpec.

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

Genera una clave secreta nueva

Para generar la clave, sigue el mismo proceso que para generar una clave privada nueva. Usa la biblioteca de Seguridad en cada caso.

Importa claves encriptadas de manera más segura

Android 9 (nivel de API 28) y versiones posteriores permiten importar claves encriptadas de forma segura al almacén de claves mediante un formato de clave con codificación ASN.1. El Keymaster luego descifra las claves en el almacén de claves, para que el contenido de estas nunca aparezca como texto sin formato en la memoria host del dispositivo. Ese 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 una máquina que sea confiable, genera el mensaje ASN.1 que debería contener 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 de Keystore

El uso del proveedor de AndroidKeyStore abarca todas las API de KeyStore estándares.

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 out what entries are
     * currently stored.
     */
    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 out what entries are
     * currently stored.
     */
    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 API 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 PrivateKey in our
     * KeyStore. This uses the X.509 certificate attached to our
     * 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 PrivateKey in our
     * KeyStore. This uses the X.509 certificate attached to our
     * 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 ha autenticado el usuario. El usuario se autentica mediante un subconjunto de sus credenciales seguras de pantalla bloqueada (patrón/PIN/contraseña y huella digital).

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 ha autenticado el usuario, esta se configura para funcionar en uno de dos modos:

  • La autenticación del usuario permite el uso de claves durante un tiempo determinado. El uso de todas las claves en este modo se autoriza una vez que el usuario desbloquea la pantalla bloqueada segura o confirma la credencial de la pantalla bloqueada segura mediante el flujo KeyguardManager.createConfirmDeviceCredentialIntent. El período de validez de la autorización es específico para cada clave y se especifica con el método setUserAuthenticationValidityDurationSeconds durante la generación o importación de la clave. Esas claves solo se pueden generar o importar si la pantalla bloqueada segura está habilitada (consulta KeyguardManager.isDeviceSecure()). Las claves se invalidan de forma permanente cuando se inhabilita la pantalla bloqueada segura (se restablece en "Ninguno", "Deslizar" o cualquier otro modo que no autentique al usuario) o se fuerza su restablecimiento (p. ej., por parte de un administrador de dispositivos).
  • La autenticación del usuario autoriza una operación criptográfica específica asociada con una clave. En este modo, el usuario debe autorizar de manera individual cada operación que incluya esa clave. Actualmente, el único medio para realizar una autorización como esta es la autenticación mediante huella dactilar: FingerprintManager.authenticate. Estas claves solo se pueden generar o importar si hay al menos una huella digital registrada (consulta FingerprintManager.hasEnrolledFingerprints). Además, se invalidan de forma permanente cuando se registra una huella digital nueva o se anulan todas.

Algoritmos compatibles

Cifrado

Algoritmo Compatibilidad (niveles de API) Notas
AES/CBC/NoPadding 23 y posteriores
AES/CBC/PKCS7Padding 23 y posteriores
AES/CTR/NoPadding 23 y posteriores
AES/ECB/NoPadding 23 y posteriores
AES/ECB/PKCS7Padding 23 y posteriores
AES/GCM/NoPadding 23 y posteriores Solo se admiten IV de 12 bytes de extensión.
RSA/ECB/NoPadding 18+
RSA/ECB/PKCS1Padding 18+
RSA/ECB/OAEPWithSHA-1AndMGF1Padding 23 y posteriores
RSA/ECB/OAEPWithSHA-224AndMGF1Padding 23 y posteriores
RSA/ECB/OAEPWithSHA-256AndMGF1Padding 23 y posteriores
RSA/ECB/OAEPWithSHA-384AndMGF1Padding 23 y posteriores
RSA/ECB/OAEPWithSHA-512AndMGF1Padding 23 y posteriores
RSA/ECB/OAEPPadding 23 y posteriores

KeyGenerator

Algoritmo Compatibilidad (niveles de API) Notas
AES 23 y posteriores Tamaños admitidos: 128, 192, 256
HmacSHA1 23 y posteriores
  • Tamaños admitidos: 8--1024 (inclusive), deben ser múltiplos de 8
  • Tamaño predeterminado: 160
HmacSHA224 23 y posteriores
  • Tamaños admitidos: 8--1024 (inclusive), deben ser múltiplos de 8
  • Tamaño predeterminado: 224
HmacSHA256 23 y posteriores
  • Tamaños admitidos: 8--1024 (inclusive), deben ser múltiplos de 8
  • Tamaño predeterminado: 256
HmacSHA384 23 y posteriores
  • Tamaños admitidos: 8--1024 (inclusive), deben ser múltiplos de 8
  • Tamaño predeterminado: 384
HmacSHA512 23 y posteriores
  • Tamaños admitidos: 8--1024 (inclusive), deben ser múltiplos de 8
  • Tamaño predeterminado: 512

KeyFactory

Algoritmo Compatibilidad (niveles de API) Notas
EC 23 y posteriores Especificaciones de claves admitidas: KeyInfo (solo clave privada), ECPublicKeySpec (solo clave pública) y X509EncodedKeySpec (solo clave pública)
RSA 23 y posteriores Especificaciones de claves admitidas: KeyInfo (solo clave privada), RSAPublicKeySpec (solo clave pública) y X509EncodedKeySpec (solo clave pública)

KeyStore

KeyStore admite los mismos tipos de claves que KeyPairGenerator y KeyGenerator.

KeyPairGenerator

Algoritmo Compatibilidad (niveles de API) Notas
DSA 19–22
EC 23 y posteriores
  • Tamaños admitidos: 224, 256, 384, 521
  • Curvas admitidas: P-224 (secp224r1), P-256 (también conocida como secp256r1 y prime256v1), P-384 (también conocida como secp384r1) y P-521 (también conocida como secp521r1)

En los niveles de API previos al 23, las claves EC pueden generarse por medio del método KeyPairGenerator del algoritmo "RSA" con la inicialización de KeyPairGeneratorSpec, cuyo tipo de clave se configura en "EC" mediante setKeyType(String). El nombre de la curva de EC no se puede especificar usando este método; se selecciona automáticamente una curva P NIST según el tamaño de clave solicitado.

RSA 18+
  • Tamaños admitidos: 512, 768, 1024, 2048, 3072, 4096
  • Exponentes públicos admitidos: 3, 65537
  • Exponente público predeterminado: 65537

Mac

Algoritmo Compatibilidad (niveles de API) Notas
HmacSHA1 23 y posteriores
HmacSHA224 23 y posteriores
HmacSHA256 23 y posteriores
HmacSHA384 23 y posteriores
HmacSHA512 23 y posteriores

Firma

Algoritmo Compatibilidad (niveles de API) Notas
MD5withRSA 18+
NONEwithECDSA 23 y posteriores
NONEwithRSA 18+
SHA1withDSA 19–22
SHA1withECDSA 19 y posteriores
SHA1withRSA 18+
SHA1withRSA/PSS 23 y posteriores
SHA224withDSA 20–22
SHA224withECDSA 20 y posteriores
SHA224withRSA 20 y posteriores
SHA224withRSA/PSS 23 y posteriores
SHA256withDSA 19–22
SHA256withECDSA 19 y posteriores
SHA256withRSA 18+
SHA256withRSA/PSS 23 y posteriores
SHA384withDSA 19–22
SHA384withECDSA 19 y posteriores
SHA384withRSA 18+
SHA384withRSA/PSS 23 y posteriores
SHA512withDSA 19–22
SHA512withECDSA 19 y posteriores
SHA512withRSA 18+
SHA512withRSA/PSS 23 y posteriores

SecretKeyFactory

Algoritmo Compatibilidad (niveles de API) Notas
AES 23 y posteriores Especificaciones de claves admitidas: KeyInfo
HmacSHA1 23 y posteriores Especificaciones de claves admitidas: KeyInfo
HmacSHA224 23 y posteriores Especificaciones de claves admitidas: KeyInfo
HmacSHA256 23 y posteriores Especificaciones de claves admitidas: KeyInfo
HmacSHA384 23 y posteriores Especificaciones de claves admitidas: KeyInfo
HmacSHA512 23 y posteriores Especificaciones de claves admitidas: KeyInfo

Artículos del blog

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