Android(安卓)指纹识别(Touch ID)实例
指纹识别
指纹识别的支持是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)权限申明
(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() { @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { mTouchIdDialog.dismiss(); //指纹验证成功 Toast.makeText(MainActivity.this, "指纹验证成功", Toast.LENGTH_SHORT).show(); } @Override public void onAuthenticationError(int errorCode, CharSequence errString) { mTouchIdDialog.dismiss(); //指纹验证失败,不可再验 Toast.makeText(MainActivity.this, "onAuthenticationError:" + errString, Toast.LENGTH_SHORT).show(); } @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { mTouchIdDialog.startIconShackAnimation(); //指纹验证失败,可再验,可能手指过脏,或者移动过快等原因。 Toast.makeText(MainActivity.this, "onAuthenticationHelp:" + helpString, Toast.LENGTH_SHORT).show(); } @Override public 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() { @Override public 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 needed if (!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 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 needed if (!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 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实例这个地址进行下载。
更多相关文章
- 【转】申请 android google map API key
- 关于AES在Android和JAVA上加密解密不能对应的问题
- 【Android】LitePal安装和使用
- Python 通过脚本获取Android的apk的部分属性,再通过加密算法生成
- android中基本的加密方法
- Android软件中嵌入地图之三:Google地图
- RSA算法 Android(安卓)JAVA C#互通
- Base64编码和AES加密
- Android实现信息安全中维吉尼亚密码技术