密码学

本文档介绍了正确使用 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,MGF1 摘要使用 SHA-1,而对于其他 Android 加密提供者,两种摘要相同。

要对您的应用使用的摘要有更多控制,请请求使用 OAEPPadding 的密码算法,例如 Cipher.getInstance("RSA/ECB/OAEPPadding"),并将 OAEPParameterSpec 提供给 init() 以显式选择两种摘要。以下代码展示了此方法。

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,并且未传递显式 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 算法标识符。