Android 6.0 ConfirmCredential

Android 6.0新出了指紋驗證Fingerprint Authenticate和確認憑證Confirm Credential,都可以用來驗證手機用戶的身份。對於指紋驗證,可以看該篇博客:FingerprintManager
在google官方提供的視頻Fingerprint and Payment APIs中講到Fingerprint Authenticate和Confirm Credential用在支付場景是比較方便的,對於Fingerprint Authenticate這種支付方式的話,谷歌官方提供了一個sample,當然樓主也寫了一篇關於指紋支付的sample,可以看該篇文章:Android KeyStore + FingerprintManager 存儲密碼
其實指紋支付的主要思想就是用通過指紋去取AndroidKeyStore中的key去解密(或加密)一段支付時需要用到的文字,只有通過解密(或加密),這段文字才能用來作爲支付的憑證。

那現在來看下如何用Confirm Credential,建議在看該篇文章前,需要先看下FingerprintManagerAndroid KeyStore + FingerprintManager 存儲密碼。對於Confirm Credential谷歌也提供了一個例子

本文章就是根據谷歌提供的例子來講解。
如果說指紋支付主要是通過指紋驗證取key的話,那Confirm Credential就是用屏幕解鎖的方式來取key,他不僅僅有指紋,還有其他的pattern(九宮格),PIN(四位密碼),Password(英文加字母)的方法來驗證用戶的身份,從而取出key。
在這個sample一開始的時候,需要先判斷當前手機是否有屏幕解鎖的方式(滑動解鎖不算,無安全性可言)。

private KeyguardManager mKeyguardManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    Button purchaseButton = (Button) findViewById(R.id.purchase_button);
    if (!mKeyguardManager.isKeyguardSecure()) {
        // Show a message that the user hasn't set up a lock screen.
        Toast.makeText(this,
                "Secure lock screen hasn't set up.\n"
                        + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen",
                Toast.LENGTH_LONG).show();
        purchaseButton.setEnabled(false);
        return;
    }
    createKey();
    findViewById(R.id.purchase_button).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Test to encrypt something. It might fail if the timeout expired (30s).
            tryEncrypt();
        }
    });
}

判斷的方式主要是通過KeyguardManager的isKeyguardSecure方法來判斷。
之後就是創建key,創建key的方法和fingeprint sample類似,這裏不再贅述。

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);
    }
}

重點看兩個方法:setUserAuthenticationRequired(true)和setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS),這兩個方法說明了使用該key的時候,需要通過用戶驗證,並且取出key的時間距離手機驗證用戶身份的時間不超過AUTHENTICATION_DURATION_SECONDS(自己設的值)。如果超時間,那麼需要你重新驗。
現在當點擊支付按鈕時,會先去AndroidKeyStore中取key,然後對預先的一段數據進行加密。

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.
        TextView textView = (TextView) findViewById(
                R.id.already_has_valid_device_credential_message);
        textView.setVisibility(View.VISIBLE);
        textView.setText(getString(
                R.string.already_confirmed_device_credentials_within_last_x_seconds,
                AUTHENTICATION_DURATION_SECONDS));
        findViewById(R.id.purchase_button).setEnabled(false);
        return true;
    } catch (UserNotAuthenticatedException e) {
        // User is not authenticated, let's authenticate with device credentials.
        Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
        if (intent != null) {
            startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
        }
        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);
    }
}

在取Key的時候和Fingerprint不一樣,在Fingerprint中需要通過調用fingerprintManager.authenticate()方法來取出,但是這裏可以直接取,如果上次驗證用戶的時間距離現在未超過設定的時間,那麼key可以直接取,但是超過了,取的時候,會拋出UserNotAuthenticatedException,這時候你需要去喚起解鎖驗證的頁面。
通過如下語句來喚起:

Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
      startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
}

@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.
        }
    }
}

然後在onActivityResult函數中,重新去獲取key,去加密文本。Confirm Credential比起Fingerprint要更加簡單。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章