Android Keystore システム

Android Keystore システムを使用すると、暗号鍵をコンテナ内に格納することで、デバイスからの鍵の抽出を難しくできます。キーストアに格納した鍵は、エクスポート不可の鍵マテリアルを使った暗号オペレーションに使用できます。さらに、Keystore システムには、鍵を使用するためにユーザー認証を求めたり、特定の暗号モードでのみ鍵を使用できるように制限したりするなど、鍵をいつどのように使用するかを制限できる機能があります。詳細については、セキュリティ機能セクションをご覧ください。

Keystore システムは、Android 4.0(API レベル 14)で導入された KeyChain API で使用されるほか、Android 4.3(API レベル 18)で導入された Android Keystore プロバイダ機能でも使用されます。このドキュメントでは、Android Keystore システムを使用するタイミングと使用方法について説明します。

セキュリティ機能

Android Keystore システムは、2 つの方法で鍵マテリアルの不正使用を防止します。1 つ目の方法では、アプリプロセスと Android デバイス全体から鍵マテリアルが抽出されないようにして、Android デバイスの外から鍵マテリアルが不正使用されるリスクを軽減します。2 つ目の方法では、アプリで鍵の正当な使用用途を指定し、その制限をアプリプロセスの外に適用して、Android デバイス内から鍵マテリアルが不正使用されるリスクを軽減します。

抽出の防止

Android Keystore の鍵の鍵マテリアルは、次の 2 つのセキュリティ対策によって抽出されないようになっています。

  • 鍵マテリアルがアプリプロセスに入ることは決してありません。アプリが Android Keystore の鍵を使用して暗号オペレーションを実行する際、バックグラウンドでは、署名や検証の対象となるプレーン テキスト、暗号テキスト、メッセージが暗号オペレーションを実行するシステム プロセスに送られます。アプリプロセスに不正侵入された場合、攻撃者がアプリの鍵を使用できるようになる可能性はありますが、鍵マテリアルを抽出することはできません(鍵マテリアルを抽出する目的としては、Android デバイスの外部で使用する場合があります)。
  • 鍵マテリアルは、高信頼実行環境(TEE)やセキュア エレメント(SE)など、Android デバイスのセキュア ハードウェアにバインドできます。鍵に対してこの機能が有効化されていると、その鍵マテリアルがセキュア ハードウェアの外部に漏洩することはありません。Android OS が侵害された場合、または攻撃者がデバイスの内部ストレージを読み取れる場合、攻撃者は Android デバイス上のアプリの Android Keystore の鍵を使用できる可能性はありますが、デバイスからこれらの鍵を抽出することはできません。この機能が有効になるのは、デバイスのセキュア ハードウェアが、鍵の使用を承認する機能(鍵アルゴリズム、ブロックモード、パディング スキーム、ダイジェスト)の特定の組み合わせをサポートしている場合に限られます。

    鍵に対してこの機能が有効になっているかどうかをチェックするには、鍵の KeyInfo を取得します。次のステップは、アプリのターゲット SDK のバージョンによって異なります。

    • アプリが Android 10(API レベル 29)以降をターゲットとしている場合は、getSecurityLevel() の戻り値を調べます。戻り値が KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT または KeyProperties.SecurityLevelEnum.STRONGBOX に一致する場合は、鍵がセキュア ハードウェア内にあることを示しています。
    • アプリが Android 9(API レベル 28)以前をターゲットとしている場合は、KeyInfo.isInsideSecurityHardware() の返されるブール値を調べます。

ハードウェア セキュリティ モジュール

Android 9(API レベル 28)以降を搭載するサポート対象デバイスには、StrongBox Keymaster があります。これは Keymaster または Keymint HAL の実装で、ハードウェア セキュリティ モジュールのようなセキュア エレメントに格納されています。ハードウェア セキュリティ モジュールは、Linux カーネルの侵害ではわからない TEE のような鍵ストレージのさまざまな実装のことを指しますが、StrongBox は明示的に埋め込みセキュア エレメント(eSE)または SoC 上のセキュア プロセッシング ユニット(iSE)などのデバイスを指します。

このモジュールには次のものが含まれています。

  • 独自の CPU。
  • 安全なストレージ。
  • 真性乱数ジェネレータ。
  • パッケージの改ざんやアプリの不正なサイドローディングを防ぐ追加のメカニズム。
  • 安全なタイマー。
  • 汎用入出力(GPIO)のような再起動通知ピン(または同等のピン)

StrongBox の省電力実装をサポートするために、アルゴリズムと鍵のサイズのサブセットがサポートされています。

  • RSA 2048
  • AES 128 および 256
  • ECDSA、ECDH P-256
  • HMAC-SHA256(8 バイトから 64 バイトまでの範囲の鍵のサイズをサポート)
  • Triple DES
  • 拡張 APDU
  • 鍵構成証明
  • アップグレードの修正 H のサポート

KeyStore クラスを使用して鍵の生成やインポートを行う場合は、setIsStrongBoxBacked() メソッドに true を渡すことで、StrongBox Keymaster 内に鍵を保存するように環境設定を指定します。

StrongBox は TEE に比べて少し遅く、リソースに制約がありますが(つまり、サポートできる同時実行オペレーションが少ない)、StrongBox は物理攻撃とサイドチャネル攻撃に対して高いセキュリティ保証を実現します。アプリのリソースの効率性よりも高いセキュリティの保証を重視する場合は、利用可能であれば、StrongBox をデバイスで使用することをおすすめします。StrongBox を利用できない場合は、TEE にフォールバックして鍵マテリアルを保存できます。

鍵の使用承認

Android デバイスで鍵が不正に使用されないようにするには、Android Keystore を使用して、鍵を生成またはインポートするときに鍵の承認された使用方法を指定します。鍵を生成またはインポートした後に、その承認を変更することはできません。その後、鍵が使用されるときは常に Android Keystore によって承認が適用されます。通常、この高度なセキュリティ機能は、鍵を生成またはインポートした後(ただし、その前やその最中ではありません)のアプリプロセスの侵害により鍵の不正使用が発生しないという要件がある場合にのみ有用です。

サポートされる鍵の使用承認は、次のカテゴリに分類されます。

  • 暗号化: 鍵は承認された鍵のアルゴリズム、オペレーション、目的(暗号化、復号、署名、検証)、パディング スキーム、ブロックモード、ダイジェストでのみ使用できます。
  • 一時的な有効期間: 定義済みの期間のみ、鍵の使用が許可されます。
  • ユーザー認証: ユーザー認証が最近行われた場合に限り、鍵を使用できます。詳細については、鍵の使用を承認するうえでユーザー認証を要求するをご覧ください。

Android デバイスによっては、鍵マテリアルがセキュア ハードウェアの内部にある鍵(KeyInfo.isInsideSecurityHardware() を参照、または Android 10(API レベル 29)以降を対象とするアプリの場合は KeyInfo.getSecurityLevel() を参照)に対する追加のセキュリティ対策として、一部の鍵の使用承認がセキュア ハードウェアによって適用されることがあります。セキュア ハードウェアは通常、暗号とユーザー認証の承認を適用します。一方、セキュア ハードウェアは通常独立したセキュア リアルタイム クロックを備えていないため、一時的な有効期間の承認を適用することはあまりありません。

鍵のユーザー認証の承認がセキュア ハードウェアによって適用されるかどうかについては、KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware() を使用してクエリできます。

キーチェーンと Android Keystore プロバイダのどちらを使用するか選択する

システムレベルでの認証情報が必要な場合は、KeyChain API を使用します。アプリが KeyChain API を通じて認証情報の使用をリクエストした場合、ユーザーはシステムが提供する UI を通じて、アプリにアクセスを許可するインストール済み認証情報を選択できます。この場合、ユーザーの同意に基づいて、複数のアプリが同一の認証情報セットを使用できます。

個々のアプリで、そのアプリだけがアクセスできる独自の認証情報を保存できるようにする場合は、Android Keystore プロバイダを使用します。これにより、そのアプリだけが使用できる認証情報をアプリが管理できるようになると同時に、KeyChain API を使用したシステムレベルでの認証情報の場合と同じセキュリティ上のメリットを実現できます。この方法では、ユーザーが認証情報を選択する必要はありません。

Android Keystore プロバイダを使用する

この機能を使用するには、標準の KeyStore クラスと、標準の KeyPairGenerator クラスまたは KeyGenerator クラス、そして Android 4.3(API レベル 18)で導入された AndroidKeyStore プロバイダを使用します。

AndroidKeyStore は、KeyStore.getInstance(type) メソッドで使用するための KeyStore タイプとして登録し、さらに KeyPairGenerator.getInstance(algorithm, provider) メソッドおよび KeyGenerator.getInstance(algorithm, provider) メソッドで使用するためのプロバイダとして登録します。

新しい秘密鍵を生成する

PrivateKey を含む新しい KeyPair を生成するには、証明書の初期 X.509 属性を指定する必要があります。KeyStore.setKeyEntry() を使用すると、後で証明書を認証局(CA)によって署名された証明書に置き換えられます。

鍵を生成するには、KeyPairGeneratorKeyGenParameterSpec を使用します。

Kotlin

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()

Java

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setDigests(KeyProperties.DIGEST_SHA256,
            KeyProperties.DIGEST_SHA512)
        .build());

KeyPair kp = kpg.generateKeyPair();

暗号化された鍵をセキュア ハードウェアにインポートする

Android 9(API レベル 28)以降では、ASN.1 でエンコードされた鍵形式を使用して、暗号化された鍵をキーストアに安全にインポートできます。Keymaster によりキーストアで鍵が復号されるため、デバイスのホストメモリに鍵のコンテンツがプレーン テキストとして表示されることはありません。このプロセスにより、鍵を復号する際のセキュリティを強化できます。

暗号鍵をキーストアに安全にインポートする機能をサポートする手順は次のとおりです。

  1. PURPOSE_WRAP_KEY 目的を使用する鍵ペアを生成します。この鍵ペアにも構成証明を追加することをおすすめします。

  2. 信頼できるサーバーまたはマシンで、SecureKeyWrapper の ASN.1 メッセージを生成します。

    ラッパーには次のスキーマが含まれます。

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. WrappedKeyEntry オブジェクトを作成して、ASN.1 メッセージをバイト配列として渡します。

  4. この WrappedKeyEntry オブジェクトを Keystore.Entry オブジェクトを受け入れる setEntry() のオーバーロードに渡します。

キーストア エントリを利用する

AndroidKeyStore プロバイダには、標準の KeyStore API すべてからアクセスできます。

エントリをリスト表示する

aliases() メソッドを呼び出すと、キーストア内のエントリをリスト表示できます。

Kotlin

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
   load(null)
}
val aliases: Enumeration<String> = ks.aliases()

Java

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();

データに署名し、検証する

データに署名するには、キーストアから KeyStore.Entry を取得して、sign() などの Signature API を使用します。

Kotlin

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry: KeyStore.Entry = ks.getEntry(alias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return null
}
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
    initSign(entry.privateKey)
    update(data)
    sign()
}

Java

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return null;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();

同様に、データを検証するには、verify(byte[]) メソッドを使用します。

Kotlin

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
val ks = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
if (entry == null) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return false
}
val valid: Boolean = Signature.getInstance("SHA256withECDSA").run {
    initVerify(entry.certificate)
    update(data)
    verify(signature)
}

Java

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return false;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(((PrivateKeyEntry) entry).getCertificate());
s.update(data);
boolean valid = s.verify(signature);

鍵の使用においてユーザー認証を要求する

鍵を生成する際や鍵を AndroidKeyStore にインポートする際、ユーザーが認証されている場合に限り、鍵の使用を承認するように指定できます。ユーザー認証は、セキュアロック画面の認証情報(パターン、PIN、パスワード、生体認証情報)のサブセットを使用して行います。

通常、この高度なセキュリティ機能は、鍵を生成またはインポートした後(ただし、その前やその最中ではありません)のアプリプロセスの侵害により、鍵を使用するためのユーザー認証がスキップされないという要件がある場合にのみ有用です。

ユーザーが認証されている場合にのみ鍵の使用が許可されている場合は、setUserAuthenticationParameters() を呼び出して、次のいずれかのモードで機能するように鍵を設定できます。

一定期間承認する
指定されたいずれかの認証情報を使用してユーザー認証がされるとすぐに、すべての鍵の使用が承認されます。
特定の暗号オペレーションでのみ承認する

鍵に関連付けられたオペレーションをユーザーが個別に承認する必要があります。

BiometricPrompt のインスタンスで authenticate() を呼び出すと、アプリでこのプロセスが開始されます。

作成する鍵ごとに、強力な生体認証情報ロック画面認証情報、その両方のタイプをサポートするかを選択できます。アプリの鍵が必要とする認証情報をユーザーがセットアップしたかどうかを確認するには、canAuthenticate() を呼び出します。

鍵が生体認証情報のみをサポートしている場合、新しい生体認証登録が追加されるたびに、鍵はデフォルトで無効になります。新しい生体認証登録が追加された場合でも、鍵を有効なままとするよう設定することもできます。そのように設定するには、falsesetInvalidatedByBiometricEnrollment() に渡します。

詳しくは、生体認証ダイアログを表示する方法など、生体認証機能をアプリに追加する方法をご覧ください。

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

ブログの記事

ICS でキーストア アクセスを統合する方法については、こちらのブログ記事をご覧ください。