密码学

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

仅与 Android Keystore 系统一起指定提供程序

如果您使用的是 Android Keystore 系统,则**必须**指定提供程序。

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

选择推荐的算法

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

建议
Cipher AES 使用 CBC 或 GCM 模式,密钥长度为 256 位(例如 AES/GCM/NoPadding
MessageDigest SHA-2 系列(例如 SHA-256
Mac SHA-2 系列 HMAC(例如 HMACSHA256
Signature SHA-2 系列与 ECDSA(例如 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,使用 SHA-1 作为 MGF1 摘要,而对于其他 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 算法标识符