指纹识别详解

(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;
    }
}
  1. 声明:

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

注意:使用对称加密还是非对称加密需要根据需求而定。

  1. 设置指纹识别返回结果监听:

     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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,649评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,621评论 18 399
  • 1、不安全的随机数生成,在CSRF TOKEN生成、password reset token生成等,会造成toke...
    nightmare丿阅读 3,691评论 0 1
  • 前阵子因为想给大福讲西游记,搜到了浙江少年儿童出版社出的《漫画中国古典四大名著》,讲了几周终于讲完了西游、三国和水...
    简记微语阅读 230评论 0 0
  • #import"ViewController.h" @interfaceViewController() @end...
    lichengjin阅读 188评论 0 0