密碼學

本文件說明 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: