KeyStore (Android) は、Java Cryptography Architecture (JCA) 暗号プロバイダの実装であり、ハードウェア分離機能を備えた安全なキーストレージのためにAndroidに統合されています。Android 4.3(API 18)以降、KeyStoreはKeymaster HALを介してハードウェアキーをサポートし、Android 9(API 28)以降は、専用のSecure Element内のキーに対してStrongBox Keymasterをサポートしています。Androidセキュリティドキュメントによると、“AndroidKeyStore”プロバイダは標準のBouncy CastleやOpenSSL KeyStoreを置き換え、不正なキー抽出に対するシステムレベルの保護を提供します。
重要なポイント
AndroidのKeyStore は、独立したアプリケーションやファイルではなく、java.security.KeyStore インターフェースを実装する暗号プロバイダです。秘密鍵、対称鍵、信頼できるCA証明書を保存および使用するための統一APIを提供します。プロバイダは “AndroidKeyStore” という名前で登録され、標準の KeyStore.getInstance() を介してアクセス可能です。
Android 4.3以前は、暗号操作はBouncy Castleを介して実行されていました。Android 4.3でKeymaster HAL 1.0が導入され、ARM TrustZoneでのTEE使用が可能になりました。Android 6.0(API 23)では、ハードウェアベースの指紋認証を備えたKeymaster 2.0が追加されました。Android 9(API 28)では、専用のSecure Element向けにKeymaster 4.0とStrongBox Keymasterが導入されました。
Keymasterの各バージョンは、新機能を追加し、キーの分離を改善します。最新のデバイス(2022年以降)は、Google Mobile Services認定のためにKeymaster 4.0をサポートする必要があり、すべてのAndroidアプリケーションにTEEの可用性を保証します。
Android KeyStoreは3つのレイヤーで構成されています:Java API(KeyStore、KeyPairGenerator)、システムプロセスkeystore(C++、システムサービスとして実行)、Keymaster HAL(TEEまたはSecure Element内のライブラリ)。アプリケーションがAPIを呼び出し、keystoreサービスがリクエストをKeymasterにルーティングし、操作が安全な環境で実行されます。
すべての秘密鍵はTEEに保存され、ユーザースペースから読み取ることはできません。システムのkeystoreサービスでさえ、生のキーにアクセスすることはできず、Keymaster内部のキーを指すハンドルにのみアクセスできます。
Android KeyStoreは、標準のJCAサービスプロバイダインターフェースを実装します。アプリケーションが Cipher.getInstance(“RSA/ECB/PKCS1Padding”, “AndroidKeyStore”) を呼び出すと、Androidセキュリティプロバイダはチェーンを介して操作をKeymasterに委任します:Java → JNI → keystoreサービス → Keymaster HAL。
AndroidKeyStore プロバイダは、プロセス起動時に自動的に登録されます。その優先順位はBouncy CastleやConscryptよりも高いため、プロバイダを指定せずにKeyStore.getInstance()を呼び出すと、ほとんどの場合AndroidKeyStoreが返されます。明示的に呼び出すには、KeyStore.getInstance(“AndroidKeyStore”) を使用します。
各Androidアプリケーションは、KeyStore内に分離されたコンテナを持ちます。同じUID(shared userId)を持つアプリケーションは特定のキーへの共有アクセスを持つことができますが、標準設定ではアプリケーションAがアプリケーションBのキーを読み取れないことが保証されています。
load(null) — KeyStoreの初期化。AndroidKeyStoreの場合、パラメータは常にnullです。setEntry — 指定されたKeyProtection(目的、ダイジェスト、パディング)でキーを保存します。getEntry — KeyStore.PrivateKeyEntry、SecretKeyEntry、またはTrustedCertificateEntryを取得します。containsAlias — キーの存在を確認します。deleteEntry — キーを完全に削除します。
import java.security.KeyStore
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
object KeyStoreManager {
private val keyStore by lazy {
KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
}
fun createRsaKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setKeySize(2048)
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.build()
val kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpg.initialize(spec)
kpg.generateKeyPair()
}
}
Android KeyStoreは、デバイスのKeymaster HALバージョンに応じて異なる幅広い暗号アルゴリズムをサポートしています。開発者は、生成を試みる際にKeyGenParameterSpec.Builderを介してサポートされるアルゴリズムのリストを取得できます。互換性のないパラメータはInvalidAlgorithmParameterExceptionをスローします。
RSA(1024~4096ビット)— 署名(SHA-1/SHA-256/SHA-384/SHA-512を使用したPKCS1、PSS)および暗号化(SHA-1/SHA-256を使用したOAEP)用。EC(P-224、P-256、P-384、P-521)— ECDSA署名およびECDH鍵共有用。X25519 および Ed25519 — Android 12(API 31)以降の最新暗号プロトコル用。
非対称キーの場合は、常にKeymaster内で生成し、秘密鍵をインポートしないでください。インポートされた秘密鍵はハードウェアで保護されず、ソフトウェア層に保存されるため、アプリケーションプロセスが侵害されると脆弱になります。
AES(128、256ビット)— CBC、CTR、GCMモードでの対称暗号化用。HMAC(SHA-1、SHA-256、SHA-512)— メッセージ認証用。ChaCha20(Android 12+)— Poly1305認証付きの高性能ストリーム暗号化用。
| アルゴリズム | Keymaster | 目的 | API |
|---|---|---|---|
| RSA | KM 1.0+ | 署名、暗号化 | 18+ |
| EC | KM 1.0+ | ECDSA、ECDH | 18+ |
| AES | KM 2.0+ | 対称暗号化 | 23+ |
| HMAC | KM 2.0+ | 認証コード | 23+ |
| ChaCha20 | KM 3.0+ | ストリーム暗号化 | 31+ |
| X25519/Ed25519 | KM 3.0+ | 鍵交換 | 31+ |
KeyStore.PrivateKeyEntry — 秘密鍵(エクスポート不可)と証明書チェーンを含みます。KeyStore.SecretKeyEntry — 対称鍵用。KeyStore.TrustedCertificateEntry — 信頼できるCA証明書用。公開鍵は keyStore.getCertificate(alias).publicKey を介してエクスポート可能です。
完全なシナリオを見てみましょう:データ暗号化用のAESキーの生成と、生体認証保護付き署名用のECキーの生成。両方のキーは、ハードウェアサポートとともにAndroid KeyStore内で作成されます。
AESキーは、KeyGenParameterSpecを指定してKeyGeneratorを介して作成されます。パラメータ:PURPOSE_ENCRYPT + PURPOSE_DECRYPT、BLOCK_MODE_GCM(認証付きの推奨モード)、ENCRYPTION_PADDING_NONE(GCMにはパディング不要)。
import javax.crypto.KeyGenerator
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
fun generateAndEncrypt(alias: String, plainText: ByteArray): ByteArray {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
val kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
kg.initialize(spec)
kg.generateKey()
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, getKeyFromStore(alias))
return cipher.doFinal(plainText)
}
userAuthenticationRequired=true のECキーは、署名操作のたびにユーザー認証を必要とします。このためには、Signatureオブジェクトを含むCryptoObjectを指定したBiometricPromptが使用されます。生体認証が成功すると、Keymasterが操作を許可します。
fun createBiometricSignKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN
).setAlgorithmParameterSpec(
ECGenParameterSpec("secp256r1")
).setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
.build()
val kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
kpg.initialize(spec)
kpg.generateKeyPair()
}
Android KeyStoreは、ソフトウェアベースのKeyStore(JKS、BKS)には提供できないハードウェアレベルのセキュリティ保証を提供します。キーはSoCレベルで保護されており、Androidユーザースペースを完全に制御しても秘密鍵を抽出することはできません。
Key Attestation は、アプリケーション(およびサーバー)がキーが作成された環境を検証できるようにするメカニズムです。Android Keystoreは、キーの特性(アルゴリズム、サイズ、目的、ハードウェアバックアップの有無、生成元)のリストを含む証明書に署名します。サーバーは、Googleルート証明書までの証明書チェーンを検証します。
これは金融アプリケーションにとって重要です。サーバーはキーがハードウェア環境で作成されたことを要求し(Hardware-Backed = True)、ソフトウェアKeystoreで作成されたキーを拒否できます。Key Attestationは、攻撃者がKeystoreをエミュレーターに置き換える攻撃を防ぎます。
setInvalidatedByBiometricEnrollment(true) は、生体認証テンプレートが変更または削除されたときにKeymasterが自動的にキーを削除することを意味します。これは、攻撃者が既存のアカウントに自分の指紋を追加する攻撃から保護します。新しい指紋が追加されると、古いキーはアクセスできなくなります。
生体認証失敗の試行回数カウンターもKeymasterによって管理されます。maxBiometricAttempt(メーカーが設定可能、通常5回)後、Keymasterは生体認証キーを使用したすべての操作を30秒間ブロックします。10回失敗すると、デバイスのパスワード(秘密のPIN)が入力されるまでブロックされます。
よくある質問
Bouncy Castle(BKS)は、パスワードで保護されたファイルにキーを保存するソフトウェアベースのKeyStoreです。Android KeyStoreはハードウェア分離TEE/StrongBoxを使用します。BKSのキーはrootアクセスで抽出できますが、Android KeyStoreのキーは抽出できません。BKSはCA証明書に適しており、Android KeyStoreは秘密鍵に適しています。
はい、生成時にPURPOSE_ENCRYPTまたはPURPOSE_DECRYPTまたはPURPOSE_SIGNまたはPURPOSE_VERIFYを指定すれば可能です。ただし、ベストプラクティスは異なる操作に別々のキーを作成することです。これにより、1つのキーが侵害された場合の損害を制限し、最小権限の原則に従います。
android.security.keystoreを介して利用可能な KeyStore.getKeyCharacteristics(alias) を使用します。このメソッドはフラグのセットを返します:FLAG_HARDWARE — TEE内のキー、FLAG_SECURE_ELEMENT — StrongBox内のキー。フラグがない場合、キーはソフトウェアのみです。
setInvalidatedByBiometricEnrollment(true) で作成されたすべてのキーは、Keymasterによって自動的に無効化されます。使用しようとすると、アプリケーションはKeyPermanentlyInvalidatedExceptionを受け取ります。これらのキーで暗号化されたデータは永久に失われます。
ハードウェアキー(TEE/StrongBox内)はバックアップをサポートしていません。これらは特定のデバイスに結びついています。ソフトウェアベースのキーはGoogle Driveバックアップに含めることができます。デバイス間でデータを転送するには、サーバー上でデータを暗号化し、新しいデバイスで復号化します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。