当前位置: 代码迷 >> 综合 >> Android 指纹识别(Touch ID)实例
  详细解决方案

Android 指纹识别(Touch ID)实例

热度:70   发布时间:2023-12-22 15:13:39.0

指纹识别

??指纹识别的支持是Android6.0以后才开始的,Google也为指纹识别提供了一些列接口,指纹识别将要用到的核心API为FingerprintManager,其中还有三个核心内部类:FingerprintManager.AuthenticationResult 指纹识别后结果的回调,FingerprintManager.AuthenticationCallback指纹识别成功失败回调, FingerprintManager.CryptoObject指纹识别加密对象。其中最难实现的为CryptoObject的创建。为了加强指纹识别的安全级别我们还可以对需要传送的密码进行加密,通过KeyStore非对称加密实现。



FingerprintManager

方法名 参数 描述
authenticate() CryptoObject,CancellationSignal,flags,AuthenticationCallback,Handler 用于开启指纹识别
isHardwareDetected() 判断指纹识别硬件是否存在且能正常使用
hasEnrolledFingerprints() 确定是否至少注册了一个指纹
  • CryptoObject 用于加密对象(可null)
  • CancellationSignal 用于取消指纹识别(可null)
  • flags 用作标记
  • AuthenticationCallback 指纹识别回调方法(可null)
  • Handler (可null)

如果只是简单测试可以只传入flags 和 AuthenticationCallback 即可



使用实例

(1)权限申明

<uses-permission android:name="android.permission.USE_FINGERPRINT"/>

(2)获取FingerprintManager对象

 /*** 获取FingerprintManager** @return FingerprintManager*/@RequiresApi(api = Build.VERSION_CODES.M)public FingerprintManager getFingerprintManagerOrNull() {if (getApplication().getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {return getApplication().getSystemService(FingerprintManager.class);} else {return null;}}

(3)创建指纹识别回调对象

 /*** 指纹识别回调监听*/private FingerprintManager.AuthenticationCallback callback = new FingerprintManager.AuthenticationCallback() {@Overridepublic void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {mTouchIdDialog.dismiss();//指纹验证成功Toast.makeText(MainActivity.this, "指纹验证成功", Toast.LENGTH_SHORT).show();}@Overridepublic void onAuthenticationError(int errorCode, CharSequence errString) {mTouchIdDialog.dismiss();//指纹验证失败,不可再验Toast.makeText(MainActivity.this, "onAuthenticationError:" + errString, Toast.LENGTH_SHORT).show();}@Overridepublic void onAuthenticationHelp(int helpCode, CharSequence helpString) {mTouchIdDialog.startIconShackAnimation();//指纹验证失败,可再验,可能手指过脏,或者移动过快等原因。Toast.makeText(MainActivity.this, "onAuthenticationHelp:" + helpString, Toast.LENGTH_SHORT).show();}@Overridepublic void onAuthenticationFailed() {mTouchIdDialog.startIconShackAnimation();//指纹验证失败,指纹识别失败,可再验,该指纹不是系统录入的指纹。Toast.makeText(MainActivity.this, "无法识别", Toast.LENGTH_SHORT).show();}};

(4)创建CancellationSignal用于取消指纹识别,开启指纹识别。

if (mFingerprintManager.isHardwareDetected() && mFingerprintManager.hasEnrolledFingerprints()) {mTouchIdStartBtn.setClickable(false);if (mTouchIdDialog == null) {mTouchIdDialog = new TouchIdDialog(MainActivity.this, R.style.TouchIdDialog);mTouchIdDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {@Overridepublic void onDismiss(DialogInterface dialog) {//如果dialog消失则取消指纹识别if (mCancellationSignal != null && isStartAuthenticate) {isStartAuthenticate = false;mCancellationSignal.cancel();mCancellationSignal = null;}mTouchIdStartBtn.setClickable(true);}});}mTouchIdDialog.show();mCancellationSignal = new CancellationSignal();if(mCryptoObjectCreator==null){initCryptoObject();}else {//开始验证指纹mFingerprintManager.authenticate(null,mCancellationSignal, 0, callback, null);isStartAuthenticate = true;}}


到目前为止

??一个简单的指纹识别demo我们就做好了,但是你们会看到我们并没用创建FingerprintManager.CryptoObject对象来进行加密,所以这样的识别是不安全的,在某种情况下会被其他具有威胁的应用破解,所以我们必须创建一个简单的指纹识别demo我们就做好了,但是你们会看到我们并没用创建FingerprintManager.CryptoObject对象来进行加密。



通过Cipher来创建FingerprintManager.CryptoObject对象

??我们将通过Cipher和KeyStore来创建加密密钥,如果应用A创建了一个key,然后应用B通过AndroidKeyStore去获取该key,是获取不到的,这样我们也就实现了指纹识别的加密。我们也可以通过keyStore来对密码进行加密。通过keyStore加密后的数据可以随意存储于任意位置即使是sharedperference中,因为即使获取到数据也获取不到密钥去对数据进行解密。

(1)创建KeyStore并初始化

private void initKeyStore(String alias){try {keyStore = KeyStore.getInstance("AndroidKeyStore");keyStore.load(null);}catch(Exception e) {e.printStackTrace();}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {createNewKeys(alias);}}

(2)创建KeyPair用于非对称加密

private void  createNewKeys(String alias){if(!"".equals(alias)){try {// Create new key if neededif (!keyStore.containsAlias(alias)) {Calendar start = Calendar.getInstance();Calendar end = Calendar.getInstance();end.add(Calendar.YEAR, 1);KeyPairGeneratorSpec spec = null;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {spec = new KeyPairGeneratorSpec.Builder(IApplication.getApplication()).setAlias(alias).setSubject(new X500Principal("CN=Sample Name, O=Android Authority")).setSerialNumber(BigInteger.ONE).setStartDate(start.getTime()).setEndDate(end.getTime()).build();}KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {generator.initialize(spec);}KeyPair keyPair = generator.generateKeyPair();}} catch (Exception e) {e.printStackTrace();}}}

(3)获取Cipher用于创建FingerprintManager.CryptoObject

public Cipher getCipher(String alias){KeyStore.PrivateKeyEntry privateKeyEntry = null;try {privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding");output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());return output;} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (UnrecoverableEntryException e) {e.printStackTrace();} catch (KeyStoreException e) {e.printStackTrace();} catch (NoSuchPaddingException e) {e.printStackTrace();} catch (InvalidKeyException e) {e.printStackTrace();}return null;}

这样FingerprintManager.CryptoObject对象我们就创建完成了,其使用方法如下

mFingerprintManager.authenticate(new FingerprintManager.CryptoObject(EncryUtils.getInstance().getCipher(mAlias)), mCancellationSignal, 0, callback, null);


KeyStore非对称加密解密

(1)加密

 /*** 加密方法* @param needEncryptWord 需要加密的字符串* @param alias 加密秘钥* @return*/public String encryptString(String needEncryptWord, String alias) {if(!"".equals(alias)&&!"".equals(needEncryptWord)){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {initKeyStore(alias);}String encryptStr="";byte [] vals=null;try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);if(needEncryptWord.isEmpty()) {return encryptStr;}Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");inCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());ByteArrayOutputStream outputStream = new ByteArrayOutputStream();CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inCipher);cipherOutputStream.write(needEncryptWord.getBytes("UTF-8"));cipherOutputStream.close();vals = outputStream.toByteArray();} catch (Exception e) {e.printStackTrace();}return Base64.encodeToString(vals, Base64.DEFAULT);}return "";}

(2)解密

 /*** 解密方法* @param needDecryptWord 需要解密的字符串* @param alias key的别称* @return*/public String decryptString(String needDecryptWord, String alias) {if(!"".equals(alias)&&!"".equals(needDecryptWord)){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {initKeyStore(alias);}String decryptStr="";try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding");output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(Base64.decode(needDecryptWord, Base64.DEFAULT)), output);ArrayList<Byte> values = new ArrayList<>();int nextByte;while ((nextByte = cipherInputStream.read()) != -1) {values.add((byte)nextByte);}byte[] bytes = new byte[values.size()];for(int i = 0; i < bytes.length; i++) {bytes[i] = values.get(i).byteValue();}decryptStr = new String(bytes, 0, bytes.length, "UTF-8");} catch (Exception e) {e.printStackTrace();}return  decryptStr;}return "";}


完整KeyStore工具类代码

package com.daobao.asus.touchiddemo.keyStoreUtil;import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.util.Base64;import com.daobao.asus.touchiddemo.IApplication;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.util.ArrayList;
import java.util.Calendar;import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.security.auth.x500.X500Principal;/*** Created by xiongyu on 2016/12/1.*  使用ksyStore加密工具类*/public class EncryUtils {static  EncryUtils encryUtilsInstance;KeyStore keyStore;public static EncryUtils getInstance() {synchronized (EncryUtils.class) {if (null == encryUtilsInstance) {encryUtilsInstance = new EncryUtils();}}return encryUtilsInstance;}private EncryUtils() {}private void initKeyStore(String alias){try {keyStore = KeyStore.getInstance("AndroidKeyStore");keyStore.load(null);}catch(Exception e) {e.printStackTrace();}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {createNewKeys(alias);}}private void  createNewKeys(String alias){if(!"".equals(alias)){try {// Create new key if neededif (!keyStore.containsAlias(alias)) {Calendar start = Calendar.getInstance();Calendar end = Calendar.getInstance();end.add(Calendar.YEAR, 1);KeyPairGeneratorSpec spec = null;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {spec = new KeyPairGeneratorSpec.Builder(IApplication.getApplication()).setAlias(alias).setSubject(new X500Principal("CN=Sample Name, O=Android Authority")).setSerialNumber(BigInteger.ONE).setStartDate(start.getTime()).setEndDate(end.getTime()).build();}KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {generator.initialize(spec);}KeyPair keyPair = generator.generateKeyPair();}} catch (Exception e) {e.printStackTrace();}}}/*** 加密方法* @param needEncryptWord 需要加密的字符串* @param alias 加密秘钥* @return*/public String encryptString(String needEncryptWord, String alias) {if(!"".equals(alias)&&!"".equals(needEncryptWord)){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {initKeyStore(alias);}String encryptStr="";byte [] vals=null;try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);if(needEncryptWord.isEmpty()) {return encryptStr;}Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");inCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());ByteArrayOutputStream outputStream = new ByteArrayOutputStream();CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inCipher);cipherOutputStream.write(needEncryptWord.getBytes("UTF-8"));cipherOutputStream.close();vals = outputStream.toByteArray();} catch (Exception e) {e.printStackTrace();}return Base64.encodeToString(vals, Base64.DEFAULT);}return "";}/*** 解密方法* @param needDecryptWord 需要解密的字符串* @param alias key的别称* @return*/public String decryptString(String needDecryptWord, String alias) {if(!"".equals(alias)&&!"".equals(needDecryptWord)){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {initKeyStore(alias);}String decryptStr="";try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding");output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(Base64.decode(needDecryptWord, Base64.DEFAULT)), output);ArrayList<Byte> values = new ArrayList<>();int nextByte;while ((nextByte = cipherInputStream.read()) != -1) {values.add((byte)nextByte);}byte[] bytes = new byte[values.size()];for(int i = 0; i < bytes.length; i++) {bytes[i] = values.get(i).byteValue();}decryptStr = new String(bytes, 0, bytes.length, "UTF-8");} catch (Exception e) {e.printStackTrace();}return  decryptStr;}return "";}/*** 获取私钥* @param alias* @return*/public PrivateKey getprivateKey(String alias){initKeyStore(alias);try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);return privateKeyEntry.getPrivateKey();} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (UnrecoverableEntryException e) {e.printStackTrace();} catch (KeyStoreException e) {e.printStackTrace();}return null;}public Cipher getCipher(String alias){KeyStore.PrivateKeyEntry privateKeyEntry = null;try {privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding");output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());return output;} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (UnrecoverableEntryException e) {e.printStackTrace();} catch (KeyStoreException e) {e.printStackTrace();} catch (NoSuchPaddingException e) {e.printStackTrace();} catch (InvalidKeyException e) {e.printStackTrace();}return null;}
}


写在最后

以上代码并不完整如果需要完整代码可以到TouchID实例这个地址进行下载。

  相关解决方案