密码学

本文档介绍了正确使用 Android 加密功能的方法,并包含一些使用示例。如果您的应用需要更强的密钥安全性,请使用 Android Keystore 系统

仅在 Android Keystore 系统中使用提供程序

如果您正在使用 Android Keystore 系统,则必须指定一个提供程序。

但是,在其他情况下,Android 不保证为给定算法提供特定提供程序。在不使用 Android Keystore 系统的情况下指定提供程序可能会导致未来版本出现兼容性问题。

选择推荐算法

当您可以自由选择要使用的算法时(例如,当您不需要与第三方系统兼容时),我们建议使用以下算法

建议
Cipher 具有 256 位密钥的 CBC 或 GCM 模式的 AES(例如 AES/GCM/NoPadding
MessageDigest SHA-2 系列(例如 SHA-256
Mac SHA-2 系列 HMAC(例如 HMACSHA256
Signature 带有 ECDSA 的 SHA-2 系列(例如 SHA256withECDSA

执行常见的加密操作

以下部分包含代码段,演示了如何在您的应用中完成常见的加密操作。

加密消息

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

生成消息摘要

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

生成数字签名

您需要有一个包含签名密钥的 PrivateKey 对象,该对象可以在运行时生成,从与应用捆绑的文件中读取,或根据您的需求从其他来源获取。

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

验证数字签名

您需要有一个包含签名者公钥的 PublicKey 对象,该对象可以从与应用捆绑的文件中读取,从证书中提取,或根据您的需求从其他来源获取。

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

实现复杂性

Android 加密实现的一些细节可能看起来不寻常,但它们的存在是出于兼容性考虑。本节讨论了您最有可能遇到的这些细节。

OAEP MGF1 消息摘要

RSA OAEP 密码通过两种不同的消息摘要进行参数化:“主”摘要和 MGF1 摘要。有一些 Cipher 标识符包含摘要名称,例如 Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"),它指定了主摘要,但未指定 MGF1 摘要。对于 Android Keystore,MGF1 摘要使用 SHA-1,而对于其他 Android 加密提供程序,这两个摘要是相同的。

为了更好地控制您的应用使用的摘要,请请求一个带有 OAEPPadding 的密码,例如 Cipher.getInstance("RSA/ECB/OAEPPadding"),并向 init() 提供一个 OAEPParameterSpec 以明确选择两个摘要。如下面的代码所示

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

已弃用的功能

以下部分介绍了已弃用的功能。请勿在您的应用中使用它们。

Bouncy Castle 算法

许多算法的 Bouncy Castle 实现已弃用。这仅影响您明确请求 Bouncy Castle 提供程序的情况,如以下示例所示

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

正如关于仅在 Android Keystore 系统中使用提供程序一节所述,不鼓励请求特定的提供程序。如果您遵循该准则,则此弃用不会影响您。

不带初始化向量的基于密码的加密密码

需要初始化向量 (IV) 的基于密码的加密 (PBE) 密码可以从密钥(如果结构合适)或明确传递的 IV 获取它。如果您传递一个不包含 IV 的 PBE 密钥并且不传递明确的 IV,则 Android 上的 PBE 密码目前会假定 IV 为零。

使用 PBE 密码时,请始终传递明确的 IV,如以下代码段所示

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

加密提供程序

自 Android 9(API 级别 28)起,Crypto Java 密码体系结构 (JCA) 提供程序已被移除。如果您的应用请求 Crypto 提供程序的实例(例如通过调用以下方法),则会发生 NoSuchProviderException

Kotlin

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

Java

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

Jetpack 安全加密库

Jetpack 安全加密库已弃用。这仅影响在您的应用模块的 build.gradle 文件中包含以下依赖项的情况

Groovy

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

Kotlin

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

支持的算法

以下是 Android 上支持的 JCA 算法标识符