(1)版本要求:
Google官方在Android6.0(API = 23)的时候提供了指纹识别接口。
(2)实现步骤:
添加权限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
构造FingerprintCore类:
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.lang.ref.WeakReference;
import com.hezuo.face.faceandfingerprintdemo.fingerprint.log.FPLog;
/**
* Created by popfisher on 2016/11/7.
*/
@TargetApi(Build.VERSION_CODES.M)
public class FingerprintCore {
private static final int NONE = 0;
private static final int CANCEL = 1;
private static final int AUTHENTICATING = 2;
private int mState = NONE;
private FingerprintManager mFingerprintManager;
private WeakReference<IFingerprintResultListener> mFpResultListener;
private CancellationSignal mCancellationSignal;
private CryptoObjectCreator mCryptoObjectCreator;
private FingerprintManager.AuthenticationCallback mAuthCallback;
private int mFailedTimes = 0;
private boolean isSupport = false;
private Handler mHandler = new Handler(Looper.getMainLooper());
/**
* 指纹识别回调接口
*/
public interface IFingerprintResultListener {
/** 指纹识别成功 */
void onAuthenticateSuccess();
/** 指纹识别失败 */
void onAuthenticateFailed(int helpId);
/** 指纹识别发生错误-不可短暂恢复 */
void onAuthenticateError(int errMsgId);
/** 开始指纹识别监听成功 */
void onStartAuthenticateResult(boolean isSuccess);
}
public FingerprintCore(Context context) {
mFingerprintManager = getFingerprintManager(context);
isSupport = (mFingerprintManager != null && isHardwareDetected());
FPLog.log("fingerprint isSupport: " + isSupport);
initCryptoObject();
}
private void initCryptoObject() {
try {
mCryptoObjectCreator = new CryptoObjectCreator(new CryptoObjectCreator.ICryptoObjectCreateListener() {
@Override
public void onDataPrepared(FingerprintManager.CryptoObject cryptoObject) {
// startAuthenticate(cryptoObject);
// 如果需要一开始就进行指纹识别,可以在秘钥数据创建之后就启动指纹认证
}
});
} catch (Throwable throwable) {
FPLog.log("create cryptoObject failed!");
}
}
public void setFingerprintManager(IFingerprintResultListener fingerprintResultListener) {
mFpResultListener = new WeakReference<IFingerprintResultListener>(fingerprintResultListener);
}
public void startAuthenticate() {
startAuthenticate(mCryptoObjectCreator.getCryptoObject());
}
public boolean isAuthenticating() {
return mState == AUTHENTICATING;
}
private void startAuthenticate(FingerprintManager.CryptoObject cryptoObject) {
prepareData();
mState = AUTHENTICATING;
try {
mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0, mAuthCallback, null);
notifyStartAuthenticateResult(true, "");
} catch (SecurityException e) {
try {
mFingerprintManager.authenticate(null, mCancellationSignal, 0, mAuthCallback, null);
notifyStartAuthenticateResult(true, "");
} catch (SecurityException e2) {
notifyStartAuthenticateResult(false, Log.getStackTraceString(e2));
} catch (Throwable throwable) {
}
} catch (Throwable throwable) {
}
}
private void notifyStartAuthenticateResult(boolean isSuccess, String exceptionMsg) {
if (isSuccess) {
FPLog.log("start authenticate...");
if (mFpResultListener.get() != null) {
mFpResultListener.get().onStartAuthenticateResult(true);
}
} else {
FPLog.log("startListening, Exception" + exceptionMsg);
if (mFpResultListener.get() != null) {
mFpResultListener.get().onStartAuthenticateResult(false);
}
}
}
private void notifyAuthenticationSucceeded() {
FPLog.log("onAuthenticationSucceeded");
mFailedTimes = 0;
if (null != mFpResultListener && null != mFpResultListener.get()) {
mFpResultListener.get().onAuthenticateSuccess();
}
}
private void notifyAuthenticationError(int errMsgId, CharSequence errString) {
FPLog.log("onAuthenticationError, errId:" + errMsgId + ", err:" + errString + ", retry after 30 seconds");
if (null != mFpResultListener && null != mFpResultListener.get()) {
mFpResultListener.get().onAuthenticateError(errMsgId);
}
}
private void notifyAuthenticationFailed(int msgId, String errString) {
FPLog.log("onAuthenticationFailed, msdId: " + msgId + " errString: " + errString);
if (null != mFpResultListener && null != mFpResultListener.get()) {
mFpResultListener.get().onAuthenticateFailed(msgId);
}
}
private void prepareData() {
if (mCancellationSignal == null) {
mCancellationSignal = new CancellationSignal();
}
if (mAuthCallback == null) {
mAuthCallback = new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
// 多次指纹密码验证错误后,进入此方法;并且,不能短时间内调用指纹验证,一般间隔从几秒到几十秒不等
// 这种情况不建议重试,建议提示用户用其他的方式解锁或者认证
mState = NONE;
notifyAuthenticationError(errMsgId, errString);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
mState = NONE;
// 建议根据参数helpString返回值,并且仅针对特定的机型做处理,并不能保证所有厂商返回的状态一致
notifyAuthenticationFailed(helpMsgId , helpString.toString());
onFailedRetry(helpMsgId, helpString.toString());
}
@Override
public void onAuthenticationFailed() {
mState = NONE;
notifyAuthenticationFailed(0 , "onAuthenticationFailed");
onFailedRetry(-1, "onAuthenticationFailed");
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
mState = NONE;
notifyAuthenticationSucceeded();
}
};
}
}
public void cancelAuthenticate() {
if (mCancellationSignal != null && mState != CANCEL) {
FPLog.log("cancelAuthenticate...");
mState = CANCEL;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
private void onFailedRetry(int msgId, String helpString) {
mFailedTimes++;
FPLog.log("on failed retry time " + mFailedTimes);
if (mFailedTimes > 5) { // 每个验证流程最多重试5次,这个根据使用场景而定,验证成功时清0
FPLog.log("on failed retry time more than 5 times");
return;
}
FPLog.log("onFailedRetry: msgId " + msgId + " helpString: " + helpString);
cancelAuthenticate();
mHandler.removeCallbacks(mFailedRetryRunnable);
mHandler.postDelayed(mFailedRetryRunnable, 300); // 每次重试间隔一会儿再启动
}
private Runnable mFailedRetryRunnable = new Runnable() {
@Override
public void run() {
startAuthenticate(mCryptoObjectCreator.getCryptoObject());
}
};
public boolean isSupport() {
return isSupport;
}
/**
* 时候有指纹识别硬件支持
* @return
*/
public boolean isHardwareDetected() {
try {
return mFingerprintManager.isHardwareDetected();
} catch (SecurityException e) {
} catch (Throwable e) {}
return false;
}
/**
* 是否录入指纹,有些设备上即使录入了指纹,但是没有开启锁屏密码的话此方法还是返回false
* @return
*/
public boolean isHasEnrolledFingerprints() {
try {
// 有些厂商api23之前的版本可能没有做好兼容,这个方法内部会崩溃(redmi note2, redmi note3等)
return mFingerprintManager.hasEnrolledFingerprints();
} catch (SecurityException e) {
} catch (Throwable e) {
}
return false;
}
public static FingerprintManager getFingerprintManager(Context context) {
FingerprintManager fingerprintManager = null;
try {
fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
} catch (Throwable e) {
FPLog.log("have not class FingerprintManager");
}
return fingerprintManager;
}
public void onDestroy() {
cancelAuthenticate();
mHandler = null;
mAuthCallback = null;
mFpResultListener = null;
mCancellationSignal = null;
mFingerprintManager = null;
if (mCryptoObjectCreator != null) {
mCryptoObjectCreator.onDestroy();
mCryptoObjectCreator = null;
}
}
}
构造CryptoObjectCreator类:
import android.annotation.TargetApi;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.util.Log;
import com.hezuo.face.faceandfingerprintdemo.fingerprint.log.FPLog;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
/**
* Created by popfisher on 2016/11/7.
*/
@TargetApi(Build.VERSION_CODES.M)
public class CryptoObjectCreator {
private static final String KEY_NAME = "crypto_object_fingerprint_key1";
private FingerprintManager.CryptoObject mCryptoObject;
private KeyStore mKeyStore;
private KeyGenerator mKeyGenerator;
private Cipher mCipher;
public interface ICryptoObjectCreateListener {
void onDataPrepared(FingerprintManager.CryptoObject cryptoObject);
}
public CryptoObjectCreator(ICryptoObjectCreateListener createListener) {
//定义KeyStore对象,指定方式
mKeyStore = providesKeystore();
//AES秘钥生成器
mKeyGenerator = providesKeyGenerator();
//采用AES(算法) + CBC(工作模式) + PADDING_PKCS7(填充模式)
mCipher = providesCipher(mKeyStore);
if (mKeyStore != null && mKeyGenerator != null && mCipher != null) {
mCryptoObject = new FingerprintManager.CryptoObject(mCipher);
}
prepareData(createListener);
}
private void prepareData(final ICryptoObjectCreateListener createListener) {
new Thread("FingerprintLogic:InitThread") {
@Override
public void run() {
try {
if (mCryptoObject != null) {
//生成Key
createKey();
// Set up the crypto object for later. The object will be authenticated by use
// of the fingerprint.
if (!initCipher()) {
FPLog.log("Failed to init Cipher.");
}
}
} catch (Exception e) {
FPLog.log(" Failed to init Cipher, e:" + Log.getStackTraceString(e));
}
if (createListener != null) {
createListener.onDataPrepared(mCryptoObject);
}
}
}.start();
}
/**
* Creates a symmetric key in the Android Key Store which can only be used after the user has
* authenticated with fingerprint.
*/
private void createKey() {
// The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
// for your flow. Use of keys is necessary if you need to know if the set of
// enrolled fingerprints has changed.
try {
mKeyStore.load(null);
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
mKeyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException e) {
FPLog.log(" Failed to createKey, e:" + Log.getStackTraceString(e));
throw new RuntimeException(e);
}
}
/**
* Initialize the {@link Cipher} instance with the created key in the {@link #createKey()}
* method.
*
* @return {@code true} if initialization is successful, {@code false} if the lock screen has
* been disabled or reset after the key was generated, or if a fingerprint got enrolled after
* the key was generated.
*/
private boolean initCipher() {
try {
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
mCipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
FPLog.log(" Failed to initCipher, e:" + Log.getStackTraceString(e));
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
FPLog.log(" Failed to initCipher, e :" + Log.getStackTraceString(e));
throw new RuntimeException("Failed to init Cipher", e);
}
}
public static KeyStore providesKeystore() {
try {
//参数:
//KeyStore.getDefaultType():该函数返回的是一个字符串,在java下,返回的是JKS,在Android下,返回的是BKS。当你使用这个keystore的时候,其文件存放在data(沙盒中)。
//AndroidKeyStore:主要是用来存储一些密钥key的,存进该处的key可以为其设置KeyProtection,例如只能通过用户验证才能取出key使用等。这些key是存在系统里的,不是在app的目录下,并且每个app不能访问其他app的key,如果app1创建了key1,并且存储的时候命名为temp,app2去通过temp去访问key,是获取不到的!!
return KeyStore.getInstance("AndroidKeyStore");
} catch (Throwable e) {
return null;
}
}
public static KeyGenerator providesKeyGenerator() {
try {
return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (Throwable e) {
return null;
}
}
public static Cipher providesCipher(KeyStore keyStore) {
try {
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (Throwable e) {
return null;
}
}
public FingerprintManager.CryptoObject getCryptoObject() {
return mCryptoObject;
}
public void onDestroy() {
mCipher = null;
mCryptoObject = null;
mCipher = null;
mKeyStore = null;
}
}
-
声明:
mFingerprintCore = new FingerprintCore(this);
需要注意的是:
CryptoObject加密对象类的创建。
-
定义KeyStore对象,指定方式
public static KeyStore providesKeystore() { try { //参数: //KeyStore.getDefaultType():该函数返回的是一个字符串,在java下,返回的是JKS,在Android下,返回的是BKS。当你使用这个keystore的时候,其文件存放在data(沙盒中)。 //AndroidKeyStore:主要是用来存储一些密钥key的,存进该处的key可以为其设置KeyProtection,例如只能通过用户验证才能取出key使用等。这些key是存在系统里的,不是在app的目录下,并且每个app不能访问其他app的key,如果app1创建了key1,并且存储的时候命名为temp,app2去通过temp去访问key,是获取不到的!! return KeyStore.getInstance("AndroidKeyStore"); } catch (Throwable e) { return null; } }
-
秘钥生成器创建(这里使用对称加密算法AES)
public static KeyGenerator providesKeyGenerator() { try { return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); } catch (Throwable e) { return null; } }
-
采用模式:AES(算法) + CBC(工作模式) + PADDING_PKCS7(填充模式)
public static Cipher providesCipher(KeyStore keyStore) { try { return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (Throwable e) { return null; } }
-
生成加密对象CryptoObject
if (mKeyStore != null && mKeyGenerator != null && mCipher != null) { mCryptoObject = new FingerprintManager.CryptoObject(mCipher); }
注意:使用对称加密还是非对称加密需要根据需求而定。
-
设置指纹识别返回结果监听:
mFingerprintCore.setFingerprintManager(mResultListener); private FingerprintCore.IFingerprintResultListener mResultListener = new FingerprintCore.IFingerprintResultListener() { @Override public void onAuthenticateSuccess() { } @Override public void onAuthenticateFailed(int helpId) { } @Override public void onAuthenticateError(int errMsgId) { } @Override public void onStartAuthenticateResult(boolean isSuccess) { } };
3.启动指纹识别:
/**
* 开始指纹识别
*/
private void startFingerprintRecognition() {
if (mFingerprintCore.isSupport()) {//判断设备是否支持指纹识别
if (!mFingerprintCore.isHasEnrolledFingerprints()) {//判断是否录制了指纹
//设备不能录制指纹,打开系统设置页面
FingerprintUtil.openFingerPrintSettingPage(this);
return;
}
if (mFingerprintCore.isAuthenticating()) {//判断是否正在录制指纹
} else {
//启动指纹识别
mFingerprintCore.startAuthenticate();
}
} else {
//设备不支持指纹解锁
}
}
4.取消指纹识别:
private void cancelFingerprintRecognition() {
if (mFingerprintCore.isAuthenticating()) {
mFingerprintCore.cancelAuthenticate();
}
}
5.销毁:
@Override
protected void onDestroy() {
if (mFingerprintCore != null) {
mFingerprintCore.onDestroy();
mFingerprintCore = null;
}
mResultListener = null;
super.onDestroy();
}
总结:
(1)这里比较重要的是如何生成加密对象CryptoObject,不太明白的可以另外在网上查找资料。
(2)这里代码比较齐全,不需要下载demo。