Criptografía

En este documento, se describe la manera adecuada de usar los recursos criptográficos de Android y se incluyen algunos ejemplos. Si tu app requiere una clave de seguridad mayor, usa el sistema de almacén de claves de Android.

Especifica un proveedor solo con el sistema Android Keystore

Si usas el sistema Android Keystore, debes especificar un proveedor.

Sin embargo, en otras situaciones, Android no garantiza un proveedor en particular para un algoritmo determinado. Especificar un proveedor sin utilizar el sistema de Android Keystore podría provocar problemas de compatibilidad en versiones futuras.

Elige un algoritmo recomendado

Si tienes la libertad de elegir qué algoritmo usar (por ejemplo, si no requieres compatibilidad con un sistema de terceros), te recomendamos que uses los siguientes algoritmos:

Clase Recomendación
Cifrado AES en modo CBC o GCM con claves de 256 bits (como AES/GCM/NoPadding)
MessageDigest Familia SHA-2 (por ejemplo, SHA-256)
Mac HMAC de la familia SHA-2 (por ejemplo, HMACSHA256)
Firma Familia SHA-2 con ECDSA (por ejemplo, SHA256withECDSA)

Ejecuta operaciones criptográficas comunes

En las siguientes secciones, se incluyen fragmentos que demuestran cómo puedes realizar operaciones criptográficas comunes en tu app.

Encripta un mensaje

Kotlin

val plaintext: ByteArray = ...
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key: SecretKey = keygen.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val ciphertext: ByteArray = cipher.doFinal(plaintext)
val iv: ByteArray = cipher.iv

Java

byte[] plaintext = ...;
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(256);
SecretKey key = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(plaintext);
byte[] iv = cipher.getIV();

Genera un resumen del mensaje

Kotlin

val message: ByteArray = ...
val md = MessageDigest.getInstance("SHA-256")
val digest: ByteArray = md.digest(message)

Java

byte[] message = ...;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(message);

Genera una firma digital

Debes tener un objeto PrivateKey que contenga la clave de firma, que puedes generar en el entorno de ejecución, leer desde un archivo incluido en tu app u obtener de alguna otra fuente según tus necesidades.

Kotlin

val message: ByteArray = ...
val key: PrivateKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initSign(key)
            update(message)
        }
val signature: ByteArray = s.sign()

Java

byte[] message = ...;
PrivateKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(key);
s.update(message);
byte[] signature = s.sign();

Verifica una firma digital

Debes tener un objeto PublicKey que contenga la clave pública del firmante, que puedes leer de un archivo incluido en tu app, extraer de un certificado, o bien obtener de alguna otra fuente según tus necesidades.

Kotlin

val message: ByteArray = ...
val signature: ByteArray = ...
val key: PublicKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initVerify(key)
            update(message)
        }
val valid: Boolean = s.verify(signature)

Java

byte[] message = ...;
byte[] signature = ...;
PublicKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(key);
s.update(message);
boolean valid = s.verify(signature);

Complejidades de implementación

Hay algunos detalles de la implementación de la criptografía de Android que parecen poco comunes, pero que se incluyen debido a problemas de compatibilidad. En esta sección, se analizan los que probablemente encontrarás.

Resumen del mensaje de OAEP MGF1

Los cifrados de RSA OAEP se parametrizan a través de dos resúmenes de mensajes diferentes: el resumen "principal" y el resumen de MGF1. Hay identificadores Cipher que incluyen nombres de resumen, como Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"), que especifica el resumen principal y deja el resumen de MGF1 sin especificar. En el caso del almacén de claves de Android, se usa SHA-1 para el resumen de MGF1, mientras que, para otros proveedores de criptografía de Android, los dos resúmenes son iguales.

Para tener más control sobre los resúmenes que utiliza tu app, solicita un algoritmo de cifrado con OAEPPadding, como en Cipher.getInstance("RSA/ECB/OAEPPadding"), y proporciona una OAEPParameterSpec a init() a fin de elegir de manera explícita ambos resúmenes. Esto se muestra en el siguiente código:

Kotlin

val key: Key = ...
val cipher = Cipher.getInstance("RSA/ECB/OAEPPadding")
        .apply {
            // To use SHA-256 the main digest and SHA-1 as the MGF1 digest
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT))
            // To use SHA-256 for both digests
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT))
        }

Java

Key key = ...;
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
// To use SHA-256 the main digest and SHA-1 as the MGF1 digest
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT));
// To use SHA-256 for both digests
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));

Funcionalidad obsoleta

En las siguientes secciones, se describe la funcionalidad obsoleta. No la uses en tu app.

Algoritmos de Bouncy Castle

Las implementaciones de Bouncy Castle de muchos algoritmos son obsoletas. Esto solo afecta a casos en los que solicitas explícitamente el proveedor de Bouncy Castle, como se muestra en el siguiente ejemplo:

Kotlin

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC")
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))

Java

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"));

Como se indicó en la sección Cómo especificar un proveedor solo con el sistema de Android Keystore, no te recomendamos que solicites un proveedor específico. Si sigues ese lineamiento, esta baja no te afectará.

Cifrados de encriptación basados en contraseñas sin un vector de inicialización

Los algoritmos de cifrado de encriptación basados en contraseñas (PBE) que requieren un vector de inicialización (IV) pueden obtenerlo de la clave, si están construidos adecuadamente, o a partir de un IV que se pase de manera explícita. Si pasas una clave de PBE que no contiene un IV y no pasas un IV explícito, los cifrados de PBE en Android del momento supondrán un IV de cero.

Cuando uses cifrados de PBE, siempre pasa un IV explícito, como se muestra en el siguiente fragmento de código:

Kotlin

val key: SecretKey = ...
val cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC")
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))

Java

SecretKey key = ...;
Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

Proveedor de criptografía

A partir de Android 9 (nivel de API 28), se quitó el proveedor de criptografía de arquitectura de criptografía de Java (JCA). Si tu app solicita una instancia del proveedor de criptografía, por ejemplo, cuando llama al método siguiente, se produce un NoSuchProviderException.

Kotlin

SecureRandom.getInstance("SHA1PRNG", "Crypto")

Java

SecureRandom.getInstance("SHA1PRNG", "Crypto");

Biblioteca criptográfica de seguridad de Jetpack

La biblioteca criptográfica de seguridad de Jetpack dejó de estar disponible. Esto solo afecta a casos en los que tienes las siguientes dependencias en el archivo build.gradle del módulo de tu app:

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.0.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.0.0")
}

Algoritmos compatibles

Estos son los identificadores de algoritmo de JCA compatibles con Android: