密碼學

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

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

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToRead),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

寫入檔案

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
String fileToWrite = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToWrite),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

加密訊息

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");

支援的演算法

以下是 Android 支援的 JCA 演算法 ID: