暗号機能

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

このドキュメントでは、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

// 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 暗号は、「main」ダイジェストと MGF1 ダイジェストの 2 つの異なるダイジェストでパラメータ化されます。Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding") などのダイジェスト名を含む Cipher 識別子があり、メインのダイジェストを指定し、MGF1 ダイジェストを指定しないようにしています。Android Keystore では SHA-1 が MGF1 ダイジェストに使用され、他の Android 暗号プロバイダでは 2 つのダイジェストは同じものです。

アプリで使用するダイジェストの制御を強化するには、Cipher.getInstance("RSA/ECB/OAEPPadding") などの OAEPPadding を使用して暗号をリクエストし、OAEPParameterSpecinit() に渡して両方のダイジェストを明示的に選択するようにします。これを次のコードで示します。

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 を持たず、IV が明示されていない PBE 鍵を渡す場合、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 Cryptography Architecture(JCA)プロバイダは削除されています。アプリが次のメソッドを呼び出すなど、暗号化プロバイダのインスタンスをリクエストした場合、NoSuchProviderException が発生します。

Kotlin

SecureRandom.getInstance("SHA1PRNG", "Crypto")

Java

SecureRandom.getInstance("SHA1PRNG", "Crypto");

サポートされているアルゴリズム

Android でサポートされている JCA アルゴリズムの識別子は以下のとおりです。