Android Keystoreは、暗号鍵をハードウェア分離環境で安全に保存するためのAndroidのシステムメカニズムです。システムはARM TrustZone搭載デバイスでTrusted Execution Environment(TEE)、または専用のSecure Elementを使用してチップレベルで鍵を保護します。Android Open Source Projectによると、Keystoreは安全な環境内で直接鍵を生成し、RSA、EC、AES、HMACアルゴリズムをサポートしています。
重要なポイント
Android Keystoreは、Android API 1(Android 1.0)から実装されている暗号プロバイダーですが、完全なハードウェアサポートはAndroid 4.3(API 18)から登場しました。Keystoreは、オペレーティングシステムが侵害された場合でも、攻撃者が平文で鍵を抽出できないように秘密鍵を安全に保存する問題を解決します。
Android Keystoreのアーキテクチャは、アプリケーションAPI(java.security.KeyStore)、システムサービス(keystoreデーモン)、ハードウェアレベル(Keymaster HAL)の3層で構成されています。アプリケーションは標準のJava Cryptography Architecture(JCA)APIを介してアクセスし、システムサービスはTEEで動作するKeymasterにリクエストをルーティングします。
鍵を使用したすべての暗号操作(署名、復号)はTEEまたはSecure Element内で実行されます。鍵は決して安全な環境から出ることはなく、アプリケーションは鍵を参照するためのハンドル(エイリアス)のみを受け取ります。これは、鍵がプロセスメモリ内でアクセス可能になる可能性があるソフトウェアKeyStoreとの基本的な違いです。
標準のJKS(Java KeyStore)またはBKS(Bouncy Castle)はパスワードで保護されたファイルに鍵を保存します。Android Keystoreはハードウェア分離環境に鍵を保存し、rootユーザーからも保護されます。JKSはファイルシステムへの直接アクセスに対して脆弱ですが、Android Keystoreはそうではありません。
もう一つの違い:Android Keystoreでは、鍵には生成時に指定された厳格な使用パラメーター(purpose — sign/verify/encrypt/decryptのみ)があります。これらは後から変更できず、鍵の誤用を防ぎます。
新しい鍵を作成する際、アプリケーションはKeyGenParameterSpec(将来の鍵のすべてのパラメーターを含む)を指定してKeyPairGeneratorまたはKeyGeneratorを呼び出します。システムはリクエストをKeymaster HALに渡し、KeymasterがTEE内で鍵を生成してハンドルを返します。
KeyGenParameterSpec.Builderメソッドは必須パラメーター(Keystore内の鍵名、目的(PURPOSE_SIGN、PURPOSE_ENCRYPT)、アルゴリズム(RSA、EC、AES))を受け入れます。追加パラメーター:digest(SHA-256)、padding(PKCS7)、userAuthenticationRequired(生体認証)、keyValidityStart/End(時間制限)。
パラメーターを設定した後、KeyPairGenerator.generateKeyPair()はKeyPairを返します。PrivateKeyはKeymasterに操作を委譲するオブジェクトです。公開鍵は抽出できますが、秘密鍵はできません。秘密鍵はTEE内にのみ存在します。
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
fun generateKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(true)
.build()
val kpGen = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpGen.initialize(spec)
kpGen.generateKeyPair()
}
ECDSAまたはRSA-PSSのSignatureは標準APIを介して作成されます:Signature.getInstance(algorithm).initSign(privateKey)。署名操作はTEEで実行され、アプリケーションがデータを渡し、Keymasterがハードウェアで署名して署名を返します。鍵とデータは共有メモリ内で混ざりません。
生体認証による保護では、署名の前にユーザーがBiometricPrompt経由で認証する必要があります。認証が成功しない場合、Keymasterは操作を実行せず、CryptoAuthenticationExceptionを返します。
import java.security.KeyStore
import java.security.Signature
import androidx.biometric.BiometricPrompt
fun signWithBiometric(alias: String) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val entry = ks.getEntry(alias, null) as KeyStore.PrivateKeyEntry
val signature = Signature.getInstance("SHA256withRSA")
signature.initSign(entry.privateKey)
// BiometricPrompt with CryptoObject(signature)がFaceID/PINを要求
}
Androidは2つの鍵ストレージモードをサポートしています:ソフトウェア(TEEなしのデバイス)とハードウェア(TEEまたはSecure Element搭載デバイス)。モードはSoCの機能とAndroidバージョンに依存します。
Trusted Execution Environmentなしのデバイス(Android 4.3以前または低価格SoC)では、鍵はロック画面パスワードから派生したマスター鍵を使用して暗号化されて保存されます。このモードは安全性が低く、暗号操作中に鍵がプロセスメモリ内でアクセス可能になります。
保護レベルはAES-256-GCMを使用したKeyStoreファイルの暗号化に基づいています。暗号鍵はユーザーのパスワードまたはPINからScrypt(高反復回数のPBKDF2)を介して生成されます。
最新のデバイスでは、TEE(ARM TrustZone)でKeymaster 4.xが使用されます。鍵はTrustZone内でのみ生成、保存、使用されます。Linuxカーネルでさえ秘密鍵にアクセスできず、Keymaster HALのみが操作を実行できます。
Secure Element(例:Samsung KnoxのeSEやGoogle Pixel 3+のStrongBox)は、独自のプロセッサとメモリを持つ独立したチップです。Common Criteria EAL 4+認定を受けており、物理的な改ざんに対する保護を含む最大レベルのセキュリティを提供します。
| タイプ | ストレージ場所 | 保護レベル | APIからの利用可能 |
|---|---|---|---|
| ソフトウェア | ファイル /data/misc/keystore | 中 (AES-256) | API 1+ |
| Keymaster 3 | TEE (TrustZone) | 高 | API 23+ |
| Keymaster 4 | TEE + Secure I/O | 非常に高 | API 28+ |
| StrongBox | ハードウェアSecure Element | 最大 | API 28+、オプション |
Android KeystoreはJava Cryptography Architecture(JCA)に統合されています。プロバイダーにアクセスするには、標準のKeyStore.getInstance("AndroidKeyStore")を使用します。APIはAPI 18から利用可能です。
KeyStore.load(null)メソッドは、アプリケーションのKeyStoreコンテナをロードします。パスワードは不要で、AndroidはアプリケーションコンテキストとそのUIDを使用してアクセス制御を行います。共有UIDが使用されていない限り、各アプリケーションは自身のエントリのみを表示します。
setEntryおよびgetEntryメソッドは、KeyStore.PrivateKeyEntry、SecretKeyEntry、TrustedCertificateEntryを操作します。ProtectionParameterパラメーターはAndroid Keystoreでは常にnullです(保護はシステムレベルで実装されています)。
import java.security.KeyStore
import java.security.cert.Certificate
import android.security.keystore.KeyProtection
fun storeSecretKey(alias: String, key: SecretKey) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val prot = KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build()
ks.setEntry(alias, KeyStore.SecretKeyEntry(key), prot)
}
KeyCharacteristicsを使用して、鍵が保存されている環境(ソフトウェアKeyStore、TEE、StrongBox)を判別できます。getKeyCharacteristics()メソッドはフラグのセットを返します:FLAG_HARDWARE(keymaster)、FLAG_SECURE_ELEMENT(StrongBox)、FLAG_TRUSTED_USER_PRESENCE_REQUIRED(生体認証)。
Android Keystoreは、非対称、対称、MACの3つのカテゴリに分かれた幅広い暗号アルゴリズムをサポートしています。特定のアルゴリズムのサポートはKeymaster HALのバージョンに依存します。
RSA(1024~4096ビット)— 署名(PKCS1、PSS)および暗号化(OAEP、PKCS1)用。EC(P-224、P-256、P-384、P-521)— ECDSA署名およびECDH鍵合意用。AES(128、256ビット)— CBC、CTR、GCMモードでの対称暗号化用。HMAC(SHA1、SHA256、SHA512)— メッセージ認証用。
各鍵に対して、setPurposesが指定され、可能な操作を制限します。PURPOSE_SIGNのRSA鍵は、攻撃者がAPIにアクセスできる場合でも暗号化に使用できません。これはハードウェアレベルでの鍵使用の強制です。
Keymasterには、生体認証の失敗試行回数のカウンターが含まれています。指定された失敗回数(setInvalidatedByBiometricEnrollmentで設定可能)を超えると、鍵は使用できなくなり、削除/再生成が必要になります。すべての生体認証テンプレートが削除されると、userAuthenticationRequired=trueのすべての鍵が自動的に無効化されます。
Key Attestation(Android 8.1+)もサポートされています。アプリケーションのリクエストに応じて、Keymasterは鍵の特性(ハードウェア/ソフトウェア、アルゴリズム、目的)に関する情報を含む証明書に署名します。サーバーはこの証明書を検証して、鍵が信頼できる環境で作成されたことを確認できます。
よくある質問
Java KeyStoreはパスワードで保護されたファイル(JKS、BKS)に鍵を保存します。Android KeystoreはTEEまたはSecure Elementを介したハードウェア分離を使用します。Java KeyStoreはrootアクセスに対して脆弱ですが、Android Keystoreは秘密鍵が安全な環境から出ることはないため脆弱ではありません。
はい、KeyProtectionを指定してKeyStore.setEntryを使用することで可能です。ただし、インポートされた鍵にはハードウェア保護がなく、マスター鍵で暗号化されたソフトウェアKeystoreに保存されます。最大限のセキュリティを得るには、常にKeystore内で鍵を生成してください。
KeyChain.isBoundKeyAlgorithmを使用するか、鍵生成後にKeyCharacteristicsを確認してください。特性にFLAG_HARDWAREが含まれている場合、鍵がTEEで作成されたことを意味します。また、android.security.keystore.isHardwareBacked()も確認できます。
アプリケーションがアンインストールされると、AndroidはKeystoreからそのすべての鍵を削除します。データは元に戻せない形で失われます。再インストール時には、アプリケーションは新しい鍵を生成する必要があります。TEEを介した鍵のバックアップはアーキテクチャ上不可能です。
ロックされたデバイスでは、Keymasterは操作を実行しません。userAuthenticationRequired=trueの鍵は毎回生体認証確認を必要とします。rootアクセスがあっても、攻撃者は直接Keymasterを呼び出すことはできず、Android Keystoreサービスのみを介してアクセスできます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。