本文档介绍了正确使用 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 算法标识符
AlgorithmParameterGenerator
AlgorithmParameters
CertPathBuilder
CertPathValidator
CertStore
CertificateFactory
Cipher
KeyAgreement
KeyFactory
KeyGenerator
KeyManagerFactory
KeyPairGenerator
KeyStore
Mac
MessageDigest
SSLContext
SSLEngine.Supported
SSLSocket.Supported
SecretKeyFactory
SecureRandom
Signature
TrustManagerFactory