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要更加简单。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值