問題の説明: BiometricPrompt 認証を使用して Android KeyStore にアクセスすると、KeyStore に対して読み取りまたは書き込み操作を実行する必要があるたびに BiometricPrompt が表示されます。iOS KeyChainで行われるのと同様の方法で、一度だけ認証し、キーストア内のデータを必要に応じて操作するソリューションを探しています。
KeyStore params ビルダーで setUserAuthenticationRequired(true) を呼び出すことにより、アプリ API AuthRefreshToken を生体認証によって保護されたアプリケーション KeyStore に格納できるように、Android 用の生体認証を実装しました (以下を参照)。Google ( https://github.com/android/security-samples/tree/main/BiometricLoginKotlin ) や他の開発者の例に従い、ソリューションを正常に機能させました。私は現在、上記の問題を 2 日目の営業日で解決しようとしていますが、成功せず、CryptoObject なしで BiometricPrompt を使用することを検討していますが、これは大きな失望です。おそらく を設定することで、一定期間一度だけ認証する方法があるparamsBuilder.setUserAuthenticationValidityDurationSeconds(30)
と思いますが、意図した結果を得ることができません。
KeyStore にアクセスして API AuthRefreshToken を読み取るには、次のコードを使用します。
biometricPrompt = BiometricPromptUtils.createBiometricPrompt(this, ::decryptServerTokenFromStorage)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
fun decryptServerTokenFromStorage(authResult: BiometricPrompt.AuthenticationResult) {
ciphertextWrapper?.let { textWrapper ->
authResult.cryptoObject?.cipher?.let {
val authRefreshToken = cryptographyManager.decryptData(textWrapper.ciphertext, it)
// Use authRefreshToken to get authToken from the API
// The API returns new authRefreshToken which has to be saved back to the KeyStore
}
}
}
すべて正常に動作し、復号化されたデータを取得します。ただし、AuthRefreshToken を使用してアプリ API で認証を行うたびに、トークンが変更されるため、すぐにそれを KeyStore に保存し直す必要があります。これが発生した場合、BiometricPrompt を再度表示する以下のコードを使用します。これにより、UI フローで BiometricPrompt が 2 回表示されます。
biometricPrompt = BiometricPromptUtils.createBiometricPrompt(this, ::encryptServerTokenToStorage)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
fun encryptServerTokenToStorage(authResult: BiometricPrompt.AuthenticationResult) {
authResult.cryptoObject?.cipher?.apply {
SampleAppUser.refreshAuthToken?.let { refreshAuthToken ->
Log.d(TAG, "The token from server is $refreshAuthToken")
val encryptedServerTokenWrapper = cryptographyManager.encryptData(refreshAuthToken, this)
// Now save encrypted authRefreshToken together with initializationVector in the app prefs for future authentications
)
}
}
}
BiometricPrompt を複数回呼び出すことなく、たとえば 1 分以上の間隔で KeyStore への完全な読み取り/書き込みアクセスができるように、BiometricPrompt で一度に認証するにはどうすればよいですか?
さまざまなアプローチを試し、暗号を再作成するか、別の目的で再初期化しようとしましたが、これらすべておよび同様の試みで、Javax.Crypto.IllegalBlockSizeException
「キーユーザーが認証されていません」というメッセージが表示されます
キーストアの初期化は次のとおりです。
// If Secretkey was previously created for that keyName, then grab and return it.
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null) // Keystore must be loaded before it can be accessed
keyStore.getKey(keyName, null)?.let { return it as SecretKey }
// if you reach here, then a new SecretKey must be generated for that keyName
val paramsBuilder = KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
paramsBuilder.apply {
setBlockModes(ENCRYPTION_BLOCK_MODE) // KeyProperties.BLOCK_MODE_GCM
setEncryptionPaddings(ENCRYPTION_PADDING) // KeyProperties.ENCRYPTION_PADDING_NONE
setKeySize(KEY_SIZE) // 256
setUserAuthenticationRequired(true) // This is required for BiometricPrompt to work properly
}
val keyGenParams = paramsBuilder.build()
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}