Android 指纹识别
Android从6.0(api = 23)系统开始就支持指纹认证功能,但在Android P (api = 28) 系统官方标记为(@Deprecated)过期,不再推荐使用,并新增BiometricPrompt接口,来做指纹识别。所以在项目开发中我们为了兼容手机版本,就必须要做好版本适配
一、Android 6.0处理
1、创建 FingerprintManager对象
FingerprintManager manager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
然后调用authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler)去验证指纹,看官方文档如下:
/** * @param crypto object associated with the call or null if none required. * @param cancel an object that can be used to cancel authentication * @param flags optional flags; should be 0 * @param callback an object to receive authentication events * @param handler an optional handler to handle callback events * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor, * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate( * BiometricPrompt.CryptoObject, CancellationSignal, Executor, * BiometricPrompt.AuthenticationCallback)} */ @Deprecated @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { authenticate(crypto, cancel, flags, callback, handler, mContext.getUserId()); }
- CryptoObject crypto:与调用关联的加密对象,如果不需要,则为空,但是这样的话,就意味这app无条件信任认证的结果,这是app被篡改的风险。
- CancellationSignal cancel:可用于取消指纹验证的对象,当在指纹验证的时候取消指纹扫描,如果不取消,就会一致扫描指纹,直到超时
- flags:图文标志,标志当前图文,默认是0
- AuthenticationCallback callback:指纹验证结果的回掉对象,通过改对象,我们处理指纹是否验证成功
- Handler handler:处理回调事件的可选处理程序,
a、CryptoObject对象创建
参考CryptoObjectHelper类,获取对象如下:
FingerprintManager.CryptoObject crypto = new CryptoObjectHelper().createCryptoObject();
@RequiresApi(Build.VERSION_CODES.M)public class CryptoObjectHelper { // This can be key name you want. Should be unique for the app. static final String KEY_NAME = "us.mifeng.fingerprint.biometric.CryptoObjectHelper"; // We always use this keystore on Android. static final String KEYSTORE_NAME = "AndroidKeyStore"; // Should be no need to change these values. static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; static final String TRANSFORMATION = KEY_ALGORITHM + File.separator + BLOCK_MODE + File.separator + ENCRYPTION_PADDING; final KeyStore _keystore; public CryptoObjectHelper() throws Exception { _keystore = KeyStore.getInstance(KEYSTORE_NAME); _keystore.load(null); } public FingerprintManager.CryptoObject createCryptoObject() throws Exception { Cipher cipher = createCipher(true); return new FingerprintManager.CryptoObject(cipher); } private Cipher createCipher(boolean retry) throws Exception { Key key = getKey(); Cipher cipher = Cipher.getInstance(TRANSFORMATION); try { cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key); } catch (KeyPermanentlyInvalidatedException e) { _keystore.deleteEntry(KEY_NAME); if (retry) { createCipher(false); } else { throw new Exception("Could not create the cipher for fingerprint authentication.", e); } } return cipher; } private Key getKey() throws Exception { Key secretKey; if (!_keystore.isKeyEntry(KEY_NAME)) { createKey(); } secretKey = _keystore.getKey(KEY_NAME, null); return secretKey; } private void createKey() throws Exception { KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME); KeyGenParameterSpec keyGenSpec = new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(BLOCK_MODE) .setEncryptionPaddings(ENCRYPTION_PADDING) .setUserAuthenticationRequired(true) .build(); keyGen.init(keyGenSpec); keyGen.generateKey(); }}
b、CancellationSignal 对象创建
CancellationSignal cancel = new CancellationSignal();
设置取消验证监听回掉
cancel.setOnCancelListener(new CancellationSignal.OnCancelListener() { @Override public void onCancel() { } });
调用cancel()方法取消验证
cancel.cancel();
c、AuthenticationCallback对象创建回掉
FingerprintManager.AuthenticationCallback callback= new FingerprintManager.AuthenticationCallback(){ @Override public void onAuthenticationError(int errorCode, CharSequence errString) { super.onAuthenticationError(errorCode, errString); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); } @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { super.onAuthenticationHelp(helpCode, helpString); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { super.onAuthenticationSucceeded(result); } }; }
对AuthenticationCallback中的方法处理:
-
onAuthenticationError:当遇到不可恢复的错误并且操作完成时调用。不会再对此对象进行回调,一般是输入次数过多或者是手机硬件出现问题
-
onAuthenticationFailed:当指纹有效但无法识别时调用。
-
onAuthenticationHelp:在身份验证过程中遇到可恢复的错误时调用。提供的帮助字符串用于为用户提供出错的指导,例如“传感器脏了,请清理它”。
-
onAuthenticationSucceeded:指纹识别成功回掉
二、Android 8.0处理
1、BiometricPrompt对象创建
google在BiometricPrompt 创建中不需要自定义UI了,Google为了统一指纹识别以及后期的生物识别,已经不允许自定义UI了,创建时必须使用BiometricPrompt.Builder
来创建对话框,其中可以自定义title、subtitle、description和一个NegativeButton(也就是cancel键)
BiometricPrompt prompt = new BiometricPrompt .Builder(this) .setTitle("Verification Title") .setDescription("Verify fingerprint to continue") .setSubtitle("") .setNegativeButton("Use Password", this.getMainExecutor(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }).build();
然后通过BiometricPrompt调用
authenticate( @NonNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)
authenticate(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)
方法,实现指纹验证
官方文档:
/** 第一个方法*/ /* @param crypto Object associated with the call * @param cancel An object that can be used to cancel authentication * @param executor An executor to handle callback events * @param callback An object to receive authentication events */ @RequiresPermission(USE_BIOMETRIC) public void authenticate(@NonNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback) { if (handlePreAuthenticationErrors(callback, executor)) { return; } mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, callback); } /** 第二个方法*/ /* @param cancel An object that can be used to cancel authentication * @param executor An executor to handle callback events * @param callback An object to receive authentication events */ @RequiresPermission(USE_BIOMETRIC) public void authenticate(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback) { if (handlePreAuthenticationErrors(callback, executor)) { return; } mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); }
2、BiometricPrompt.CryptoObject
参考参考CryptoObjectHelper2类,获取对象如下:
BiometricPrompt.CryptoObject cryptoObject = new CryptoObjectHelper2().createCryptoObject();
CryptoObjectHelper2类如下:
@RequiresApi(Build.VERSION_CODES.P)public class CryptoObjectHelper2 { private static final String KEY_NAME = "BiometricPromptApi28"; public BiometricPrompt.CryptoObject createCryptoObject() throws Exception { KeyPair keyPair = generateKeyPair(KEY_NAME,true); String mToBeSignedMessage = new StringBuilder() .append(Base64.encodeToString(keyPair.getPublic().getEncoded(), Base64.URL_SAFE)) .append(":") .append(KEY_NAME) .append(":") // Generated by the server to protect against replay attack .append("12345") .toString(); Signature mSignature = initSignature(KEY_NAME); return new BiometricPrompt.CryptoObject(mSignature); } private Signature initSignature(String keyName) throws Exception { KeyPair keyPair = getKeyPair(keyName); if (null != keyPair){ Signature signature = Signature.getInstance("SHA256withECDSA"); signature.initSign(keyPair.getPrivate()); return signature; } return null; } private KeyPair getKeyPair(String keyName) throws Exception{ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); if (keyStore.containsAlias(keyName)){ PublicKey publicKey = keyStore.getCertificate(keyName).getPublicKey(); PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyName,null); return new KeyPair(publicKey,privateKey); } return null; } private KeyPair generateKeyPair(String keyName, boolean invaldateByBiometricEnrollment) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC,"AndroidKeyStore"); KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,KeyProperties.PURPOSE_SIGN) .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512) .setUserAuthenticationRequired(true) .setInvalidatedByBiometricEnrollment(invaldateByBiometricEnrollment); keyPairGenerator.initialize(builder.build()); return keyPairGenerator.generateKeyPair(); } }
更多相关文章
- Android保证首次获取到的location对象不为空的解决方案
- android中能不能new Activity()对象引发的思考
- android 使用socket与pc传递对象的问题
- Android探索之旅 | 面向对象和Java基础
- Android对Window对象的管理机制分析
- android 5.0之后利用Intent传递Serializable对象存在问题