本文件說明 Android 加密編譯功能的正確使用方式,並提供相關使用範例。如果您的應用程式需要更高的金鑰安全性,請使用 Android KeyStore 系統。
只在使用 Android KeyStore 系統時指定提供者
如果您使用 Android KeyStore 系統,就必須指定提供者。
但在其他情況下,Android 無法保證採用特定提供者的指定演算法。如果在未使用 Android KeyStore 系統的情況下指定提供者,可能導致日後版本發生相容性問題。
選擇建議的演算法
如果您可以自由選擇要使用的演算法 (例如不需要與第三方系統相容時),建議您使用下列演算法:
類別 | 建議 |
---|---|
加密 | CBC 或 GCM 模式的 AES,含 256 位元金鑰(例如 AES/GCM/NoPadding ) |
MessageDigest | SHA-2 系列 (例如 SHA-256 ) |
Mac | SHA-2 系列 HMAC (例如 HMACSHA256 ) |
簽名 | 使用 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
ID 包含摘要名稱,例如 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 中取得。如果您傳遞的 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)起,已移除加密 Java 密碼編譯架構 (JCA) 的提供者。如果應用程式要求一個加密編譯提供者的執行個體(例如呼叫下列方法),就會發生 NoSuchProviderException
。
Kotlin
SecureRandom.getInstance("SHA1PRNG", "Crypto")
Java
SecureRandom.getInstance("SHA1PRNG", "Crypto");
Jetpack Security 加密編譯程式庫
Jetpack Security 加密編譯程式庫已淘汰。但請放心,這只會在應用程式模組的 build.gradle
檔案中含有下列依附元件時,才會造成影響:
Groovy
dependencies { implementation "androidx.security:security-crypto:1.0.0" }
Kotlin
dependencies { implementation("androidx.security:security-crypto:1.0.0") }
支援的演算法
以下是 Android 支援的 JCA 演算法 ID:
AlgorithmParameterGenerator
AlgorithmParameters
CertPathBuilder
CertPathValidator
CertStore
CertificateFactory
Cipher
KeyAgreement
KeyFactory
KeyGenerator
KeyManagerFactory
KeyPairGenerator
KeyStore
Mac
MessageDigest
SSLContext
SSLEngine.Supported
SSLSocket.Supported
SecretKeyFactory
SecureRandom
Signature
TrustManagerFactory