こんにちは、KID.Aです。
Android 6.0からデバイスのロック解除後に、ユーザを認証して再度ロック解除しなくても一定期間ならば使えるよう方法がAPIで提供されるようになりました。
・使用するAPIはこちらになります。
http://developer.android.com/intl/ja/reference/android/security/keystore/KeyGenParameterSpec.html
また、以下にサンプルが提供されています。
今回はこちらを使用して説明していきたいと思います。
https://github.com/googlesamples/android-ConfirmCredential
アプリ動作
まずはアプリの動作説明をします。
起動すると以下のような画面がでます。
・PURCHASEを押下でパターン確認がでます。ロックを解除します。
・アプリを終了させ、もう一度起動します。
・一度パターン確認したらPURCHASEを押下しても、30秒間はパターン確認をしなくてもよくなります。
実装確認
実装のポイントは以下の通りです。
起動時にcreateKey()でAndroidKeyStoreにキーを作ります。
setUserAuthenticationValidityDurationSecondsで有効期間が指定できます。
AUTHENTICATION_DURATION_SECONDSは30のintになっておりこのサンプルでは有効期間が30秒になっています。
private void createKey() { // Generate a key to decrypt payment credentials, tokens, etc. // This will most likely be a registration step for the user when they are setting up your app. try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); KeyGenerator keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); // Set the alias of the entry in Android KeyStore where the key will appear // and the constrains (purposes) in the constructor of the Builder keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) // Require that the user has unlocked in the last 30 seconds .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .build()); keyGenerator.generateKey(); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException | KeyStoreException | CertificateException | IOException e) { throw new RuntimeException("Failed to create a symmetric key", e); } }
次にボタンが押されたら、tryEncrypt()が呼ばれてAndroidKeyStoreの検証を行います。
private boolean tryEncrypt() { try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); // Try encrypting something, it will only work if the user authenticated within // the last AUTHENTICATION_DURATION_SECONDS seconds. cipher.init(Cipher.ENCRYPT_MODE, secretKey); cipher.doFinal(SECRET_BYTE_ARRAY); // If the user has recently authenticated, you will reach here. showAlreadyAuthenticated(); return true; } catch (UserNotAuthenticatedException e) { // User is not authenticated, let's authenticate with device credentials. showAuthenticationScreen(); return false; } catch (KeyPermanentlyInvalidatedException e) { // This happens if the lock screen has been disabled or reset after the key was // generated after the key was generated. Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" + e.getMessage(), Toast.LENGTH_LONG).show(); return false; } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException(e); } }
もしこの際にまだAndroidKeyStoreにキーが作れていない場合は、下記の箇所で例外が発生します。
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
例外が発生した場合は以下のコードで認証を行います。
Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); if (intent != null) { startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); }
認証の結果はonActivityResultにかえってきます。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { // Challenge completed, proceed with using cipher if (resultCode == RESULT_OK) { if (tryEncrypt()) { showPurchaseConfirmation(); } } else { // The user canceled or didn’t complete the lock screen // operation. Go to error/cancellation flow. } } }
30秒以内でしたら、
tryEncrypt()のcipher.init(Cipher.ENCRYPT_MODE, secretKey);
でキーが作れている状態になるので例外が発生しないので、これで認証されているか判断します。
以上です。