指紋識别
指紋識别的支援是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() {
@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<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 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<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執行個體這個位址進行下載下傳。