Cryptography

This document describes the proper way to use Android's cryptographic facilities and includes some examples of their use. If your app requires greater key security, use the Android Keystore system.

Specify a provider only with the Android Keystore system

If you're using the Android Keystore system, you must specify a provider.

In other situations, however, Android doesn't guarantee a particular provider for a given algorithm. Specifying a provider without using the Android Keystore system can cause compatibility problems in future releases.

Choose a recommended algorithm

When you have the freedom to choose which algorithm to use (such as when you don't require compatibility with a third-party system), we recommend using the following algorithms:

Class Recommendation
Cipher AES in either CBC or GCM mode with 256-bit keys (such as AES/GCM/NoPadding)
MessageDigest SHA-2 family (such as SHA-256)
Mac SHA-2 family HMAC (such as HMACSHA256)
Signature SHA-2 family with ECDSA (such as SHA256withECDSA)

Perform common cryptographic operations

The following sections include snippets that demonstrate how you can complete common cryptographic operations in your app.

Encrypt a message

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

Generate a message digest

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

Generate a digital signature

You need to have a PrivateKey object containing the signing key, which you can generate at runtime, read from a file bundled with your app, or obtain from some other source depending on your needs.

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

Verify a digital signature

You need to have a PublicKey object containing the signer's public key, which you can read from a file bundled with your app, extract from a certificate, or obtain from some other source depending on your needs.

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

Implementation complexities

There are some details of the Android cryptography implementation that seem unusual but are present due to compatibility concerns. This section discusses the ones that you'll most likely encounter.

OAEP MGF1 message digest

RSA OAEP ciphers are parameterized by two different message digests: the “main” digest and the MGF1 digest. There are Cipher identifiers that include digest names, such as Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"), which specifies the main digest and leaves the MGF1 digest unspecified. For Android Keystore, SHA-1 is used for the MGF1 digest, whereas for other Android cryptographic providers, the two digests are the same.

To have more control over the digests that your app uses, request a cipher with OAEPPadding, as in Cipher.getInstance("RSA/ECB/OAEPPadding"), and provide an OAEPParameterSpec to init() to explicitly choose both digests. This is shown in the code that follows:

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

Deprecated functionality

The following sections describe deprecated functionality. Don't use it in your app.

Bouncy Castle algorithms

The Bouncy Castle implementations of many algorithms are deprecated. This only affects cases where you explicitly request the Bouncy Castle provider, as shown in the following example:

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

As noted in the section about specifying a provider only with the Android Keystore system, requesting a specific provider is discouraged. If you follow that guideline, this deprecation doesn't affect you.

Password-based encryption ciphers without an initialization vector

Password-based encryption (PBE) ciphers that require an initialization vector (IV) can obtain it from the key, if it's suitably constructed, or from an explicitly passed IV. If you pass a PBE key that doesn't contain an IV and don't pass an explicit IV, the PBE ciphers on Android currently assume an IV of zero.

When using PBE ciphers, always pass an explicit IV, as shown in the following code snippet:

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

Crypto provider

As of Android 9 (API level 28), the Crypto Java Cryptography Architecture (JCA) provider has been removed. If your app requests an instance of the Crypto provider, such as by calling the following method, a NoSuchProviderException occurs.

Kotlin

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

Java

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

Jetpack security crypto library

Jetpack security crypto library is deprecated. This only affects cases where you have the following dependencies in your app module's build.gradle file:

Groovy

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

Kotlin

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

Supported algorithms

These are the JCA algorithm identifiers that are supported on Android: