指纹解锁
概念
据人指纹验能解锁否技术。类似输密码(都通一定数字特征解锁)解锁。
原理
每人每指纹信息独一无二,皮肤纹路在图案、断点、交叉点各不同,光学感应读指纹图片信息亦独一无二。先存一人指纹特征(图片信息据算法生成),用时拿该人指纹特征对比先前存指纹特征,对比一致解锁成功,不一致解锁失败。结果并非100%而可按比例判,如90%也可判一致,可硬件设。
优点
使用便捷,直通自身生物特征认证,速度快;输数字或拼图解锁麻烦且易被看到。
缺点
安全性不高,指纹可被收集,一旦别人收集你指纹信息则需你指纹解锁东西将极危险且不注意情况易被有心人收集。
发展
Android6.0(Api23)加指纹识别API,即FingerprintManager
,定义最基础指纹识别接口。但AndroidP(Api28)官方不推荐用,做@Deprecated处理。后来support v4库添FingerprintManagerCompat
类,对FingerprintManager一定封装,如判SDK版、处理加密部分等。本质仍用FingerprintManager实现指纹识别。AndroidP,FingerprintManager正式退役。系统新增BiometricPrompt
接口,接口名生物识别
可看出今后安全验证将不局限指纹,应还加面部识别等。
API
6.0
FingerprintManager协调管理和访问指纹识别硬件设备
复制代码
FingerprintManager.AuthenticationCallback
指纹认证后系统回调该接口通知应用认证结果
复制代码
FingerprintManager.AuthenticationResult
表认证结果类,回调接口中以参数给出
复制代码
FingerprintManager.CryptoObject
加密对象类保认证安全
复制代码
P
Android P中FingerprintDialog及其含类将替代FingerprintManager及其含类。方法
屏幕锁
KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager.isKeyguardSecure()) {
// this device is secure.
}
复制代码
设备须屏幕锁保护,屏幕锁可password、PIN或图案。谷歌原生逻辑为用指纹识别须先屏幕锁可用才可,等同5.0中smart lock逻辑。这是因谷歌认为目前指纹识别技术仍不足,安全性不能同传统方式比较,可如上代码查当前设备安全保护中否。
FingerprintManager对象
// Using the Android Support Library v4(谷歌推荐)
fingerprintManager = FingerprintManagerCompat.from(this);
// Using API level 23:
fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);
复制代码
执行
/**
* @param crypto object associated with the call or null if none required. // 加密指纹特征,可不加密置null
* @param flags optional flags; should be 0 // 设标记,暂无用
* @param cancel an object that can be used to cancel authentication // 取消验证
* @param callback an object to receive authentication events // 系统认证后回调该接口
* @param handler an optional handler for events // callback后界面处理,默主线程handler,可null
*/
fingerprintManagerCompat.authenticate(
cryptoObjectHelper.buildCryptoObject(),
0,
new CancellationSignal(),
new AuthenticateCallback(HandleCooperate.stepHandle(sweetAlertDialog)),
null);
复制代码
取消
/**
* 指纹认证取消
*
* @param cancellationSignal cancellationSignal
*/
public static void fingerprintAuthenticateCancel(CancellationSignal cancellationSignal) {
if (!cancellationSignal.isCanceled()) {
cancellationSignal.cancel();
}
}
复制代码
加密
场景
- 本地识别 本地完成指纹识别后同本地信息绑定登录
- 后台交互 本地完成识别后传数据至服务器。无论本地还是与服务器交互,都需加密信息。通与本地交互用对称加密,与服务器交互用非对称加密。
对称加密
单密钥密码系统法,同一密钥作为加密和解密工具。通密钥控制加密和解密的指令,算法规定如何加密和解密。优点为算法公开、加密解密速度快、效率高;缺点为发送前双方统一密钥,泄露则不安全。如AES、DES加密算法等。
非对称加密
非对称加密算法需两密钥进行加密和解密,两秘钥为公开密钥(简称公钥)和私有密钥(简称私钥)。一方公钥加密,接受方私钥解密;反之发送方私钥加密,接收方公钥解密。因加密和解密用不同密钥,故称非对称加密算法。相比对称加密算法,非对称加密安全性很大提升,但效率低很多。解密加密花费时间更长,适合少量数据加密。如RSA、ECC加密算法等。
实现
AndroidManifest.xml
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
复制代码
fingerprint
AuthenticateCallback
package fingerprint;
import android.os.Handler;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
/**
* Created on 2018/8/18.
*
* @desc 认证回调
*/
public class AuthenticateCallback extends FingerprintManagerCompat.AuthenticationCallback {
private Handler handler;
AuthenticateCallback(Handler handler) {
super();
this.handler = handler;
}
/**
* 验证错误
*/
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
super.onAuthenticationError(errMsgId, errString);
if (handler != null) {
handler.obtainMessage(0, errMsgId, 0).sendToTarget();
}
}
/**
* 验证帮助
*/
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
super.onAuthenticationHelp(helpMsgId, helpString);
if (handler != null) {
handler.obtainMessage(1, helpMsgId, 0).sendToTarget();
}
}
/**
* 验证成功
*/
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
if (handler != null) {
handler.obtainMessage(2).sendToTarget();
}
// FIXME: 2018/8/21 报错
/*try {
result.getCryptoObject().getCipher().doFinal();
if (handler != null) {
handler.obtainMessage(2).sendToTarget();
}
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}*/
}
/**
* 验证失败
*/
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
if (handler != null) {
handler.obtainMessage(3).sendToTarget();
}
}
}
复制代码
AuthenticateHandle
package fingerprint;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.Message;
import android.support.v4.os.CancellationSignal;
import com.self.zsp.dfs.subject.MainActivity;
import application.App;
import util.DateFormatUtils;
import util.DateUtils;
import util.LogUtils;
import util.SharedPrefUtils;
import util.ToastUtils;
import value.Flag;
import widget.dialog.one.SweetAlertDialog;
/**
* Created on 2018/8/18.
*
* @desc 配合处理
*/
public class AuthenticateHandle {
/**
* 初始Handle
*
* @return Handler
*/
@SuppressLint("HandlerLeak")
public static Handler stepHandle(final Context context, final SweetAlertDialog sweetAlertDialog) {
return new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LogUtils.e("msg: " + msg.what + " arg1: " + msg.arg1);
// FIXME: 2018/8/21 文本改引用
switch (msg.what) {
// error
case 0:
handleErrorCode(context, msg.arg1, sweetAlertDialog);
break;
// help
case 1:
handleHelpCode(msg.arg1);
break;
// succeeded
case 2:
sweetAlertDialog.setTitleText("结果").setContentText("验证成功").setConfirmText("即将进入").changeAlertType(2);
// 延时关弹框(否动变进行致关失败)
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
sweetAlertDialog.dismiss();
Activity activity = (Activity) context;
activity.startActivity(new Intent(activity, MainActivity.class));
activity.finish();
}
}, 1000);
sweetAlertDialog.show();
break;
// failed
case 3:
int tempNumber = App.getInstance().getFingerprintAuthenticateNumber();
tempNumber--;
sweetAlertDialog.setTitleText("提示").setContentText("指纹不匹配,还可以尝试" + tempNumber + "次").setConfirmText("取消").changeAlertType(3);
App.getInstance().setFingerprintAuthenticateNumber(tempNumber);
sweetAlertDialog.show();
break;
default:
break;
}
}
};
}
/**
* 错误
*
* @param context 上下文
* @param code 码
*/
private static void handleErrorCode(Context context, int code, SweetAlertDialog sweetAlertDialog) {
switch (code) {
// 1
case FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE:
sweetAlertDialog.setTitleText("提示").setContentText("当前设备不可用,稍后再试").setConfirmText("知道了").changeAlertType(1);
break;
// 2
case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
sweetAlertDialog.setTitleText("提示").setContentText("传感器不能处理当前指纹图片").setConfirmText("知道了").changeAlertType(1);
break;
// 3
case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT:
sweetAlertDialog.setTitleText("提示").setContentText("操作超时,一般为30秒").setConfirmText("知道了").changeAlertType(1);
break;
// 4
case FingerprintManager.FINGERPRINT_ERROR_NO_SPACE:
sweetAlertDialog.setTitleText("提示").setContentText("无足够空间保存本次操作").setConfirmText("知道了").changeAlertType(1);
break;
// 5
case FingerprintManager.FINGERPRINT_ERROR_CANCELED:
LogUtils.e("传感器不可用,操作取消");
break;
// 7
case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT:
sweetAlertDialog.setTitleText("提示").setContentText("指纹已禁用,30秒后再试").setConfirmText("知道了").changeAlertType(1);
SharedPrefUtils.saveString(context, Flag.FINGERPRINT_AUTHENTICATE_ERROR_LOCK_OUT_DATE, DateUtils.getCurrentTime(DateFormatUtils.DATE_FORMAT_SIX));
break;
default:
break;
}
}
/**
* 帮助
*
* @param code code
*/
private static void handleHelpCode(int code) {
switch (code) {
// 0
case FingerprintManager.FINGERPRINT_ACQUIRED_GOOD:
ToastUtils.shortShow("获取指纹良好");
break;
// 1
case FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL:
ToastUtils.shortShow("仅获取部分指纹");
break;
// 2
case FingerprintManager.FINGERPRINT_ACQUIRED_INSUFFICIENT:
ToastUtils.shortShow("获取指纹不完整");
break;
// 3
case FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
ToastUtils.shortShow("建议清洁感应器");
break;
// 4
case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_SLOW:
ToastUtils.shortShow("手指移动太慢");
break;
// 5
case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST:
ToastUtils.shortShow("手指移动太快");
break;
default:
break;
}
}
/**
* 指纹认证取消
*
* @param cancellationSignal cancellationSignal
*/
public static void fingerprintAuthenticateCancel(CancellationSignal cancellationSignal) {
if (!cancellationSignal.isCanceled()) {
cancellationSignal.cancel();
}
}
}
复制代码
CryptoObjectHelper
package fingerprint;
import android.annotation.TargetApi;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import java.security.Key;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
/**
* Created on 2018/8/18.
* 新建一KeyStore密钥库(存密钥)
* 获KeyGenerator密钥生成工具(生密钥)
* 通密钥初始Cipher对象(生加密对象CryptoObject)
* authenticate()启指纹传感器并监听
*
* @desc CryptoObjectHelper
*/
@TargetApi(Build.VERSION_CODES.M)
public class CryptoObjectHelper {
/**
* This can be key name you want. Should be unique for the app.
*/
private static final String KEY_NAME = "com.self.zsp.dfs.fingerprint_authentication_key";
/**
* We always use this keystore on Android.
*/
private static final String KEYSTORE_NAME = "AndroidKeyStore";
/**
* Should be no need to change these values.
*/
private static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
private static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
private static final String TRANSFORMATION = KEY_ALGORITHM + "/" + BLOCK_MODE + "/" + ENCRYPTION_PADDING;
private final KeyStore keystore;
CryptoObjectHelper() throws Exception {
keystore = KeyStore.getInstance(KEYSTORE_NAME);
keystore.load(null);
}
public FingerprintManagerCompat.CryptoObject buildCryptoObject() throws Exception {
Cipher cipher = createCipher(true);
return new FingerprintManagerCompat.CryptoObject(cipher);
}
private Cipher createCipher(boolean retry) throws Exception {
Key key = getKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
try {
cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key);
} catch (KeyPermanentlyInvalidatedException e) {
keystore.deleteEntry(KEY_NAME);
if (retry) {
createCipher(false);
} else {
throw new Exception("Could not create the cipher for fingerprint authentication.", e);
}
}
return cipher;
}
private Key getKey() throws Exception {
Key secretKey;
if (!keystore.isKeyEntry(KEY_NAME)) {
createKey();
}
secretKey = keystore.getKey(KEY_NAME, null);
return secretKey;
}
@TargetApi(Build.VERSION_CODES.M)
private void createKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME);
KeyGenParameterSpec keyGenSpec =
new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE)
.setEncryptionPaddings(ENCRYPTION_PADDING)
.setUserAuthenticationRequired(true)
.build();
keyGen.init(keyGenSpec);
keyGen.generateKey();
}
}
复制代码
FingerprintAuthenticate
package fingerprint;
import android.content.Context;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import widget.dialog.one.SweetAlertDialog;
/**
* Created on 2018/8/18.
*
* @desc 指纹认证
*/
public class FingerprintAuthenticate {
/**
* 指纹认证状(1不支持、2支持但无指纹)
*/
private int fingerprintAuthenticateState;
/**
* 判断
*/
public int judge(Context context) {
// 官方荐v4包API(内已判系统6.0+否)
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(context);
// Determine if fingerprint hardware is present and functional.
// @return true if hardware is present and functional, false otherwise.
if (fingerprintManagerCompat.isHardwareDetected()) {
// Determine if there is at least one fingerprint enrolled.
// @return true if at least one fingerprint is enrolled, false otherwise
if (!fingerprintManagerCompat.hasEnrolledFingerprints()) {
fingerprintAuthenticateState = 2;
}
} else {
fingerprintAuthenticateState = 1;
}
return fingerprintAuthenticateState;
}
/**
* 执行
*/
public void execute(Context context, CancellationSignal cancellationSignal, SweetAlertDialog sweetAlertDialog) {
// 官方荐v4包API(内已判系统6.0+否)
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(context);
// Determine if fingerprint hardware is present and functional.
// @return true if hardware is present and functional, false otherwise.
if (fingerprintManagerCompat.isHardwareDetected()) {
// Determine if there is at least one fingerprint enrolled.
// @return true if at least one fingerprint is enrolled, false otherwise
if (fingerprintManagerCompat.hasEnrolledFingerprints()) {
if (sweetAlertDialog != null) {
sweetAlertDialog.show();
try {
CryptoObjectHelper cryptoObjectHelper = new CryptoObjectHelper();
fingerprintManagerCompat.authenticate(
cryptoObjectHelper.buildCryptoObject(),
0,
cancellationSignal,
new AuthenticateCallback(AuthenticateHandle.stepHandle(context, sweetAlertDialog)),
null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
复制代码
使用
判断
/**
* 指纹认证
*/
private void fingerprintAuthenticate() {
FingerprintAuthenticate fingerprintAuthenticate = new FingerprintAuthenticate();
switch (fingerprintAuthenticate.judge(this)) {
// 不支持
case 1:
ZsDialog.materialContentDialogOneClick(this, R.string.fingerprintRecognitionNotSupported, R.string.know)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
}).cancelable(false).show();
break;
// 支持但无指纹
case 2:
ZsDialog.materialContentDialogTwoClick(this, R.string.noInputFingerprint, R.string.goToEnter, R.string.laterOn, R.color.fontHint)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
}).cancelable(false).show();
break;
efault:
String fingerprintAuthenticateSwitch = SharedPrefUtils.getString(this, Flag.FINGERPRINT_AUTHENTICATE_SWITCH, null);
// 已开启
if (fingerprintAuthenticateSwitch != null && Magic.STRING_OPEN.equals(fingerprintAuthenticateSwitch)) {
ZsDialog.materialContentDialogOneClick(this, R.string.fingerprintAuthenticateAlreadyOpen, R.string.know)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
}).cancelable(false).show();
} else {
// 未开启
ZsDialog.materialContentDialogTwoClick(this, R.string.fingerprintAuthenticateOpenHint, R.string.open, R.string.cancel, R.color.fontHint)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
SharedPrefUtils.saveString(MainActivity.this, Flag.FINGERPRINT_AUTHENTICATE_SWITCH, Magic.STRING_OPEN);
toastShort(getString(R.string.fingerprintAuthenticateOpenSuccess));
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
}).cancelable(false).show();
}
break;
}
}
复制代码
验证
/**
* 指纹认证
*/
private FingerprintAuthenticate fingerprintAuthenticate;
private CancellationSignal cancellationSignal;
private SweetAlertDialog sweetAlertDialogFingerprintAuthenticate;
// 指纹认证
fingerprintAuthenticate = new FingerprintAuthenticate();
cancellationSignal = new CancellationSignal();
/**
* 指纹认证
*/
private void fingerprintAuthenticate() {
// 已开启
String fingerprintAuthenticateSwitch = SharedPrefUtils.getString(this, Flag.FINGERPRINT_AUTHENTICATE_SWITCH, null);
if (fingerprintAuthenticateSwitch != null && Magic.STRING_OPEN.equals(fingerprintAuthenticateSwitch)) {
// FINGERPRINT_ERROR_LOCKOUT
String fingerprintAuthenticateErrorLockOutTime = SharedPrefUtils.getString(this, Flag.FINGERPRINT_AUTHENTICATE_ERROR_LOCK_OUT_DATE, null);
if (fingerprintAuthenticateErrorLockOutTime != null) {
int secondSeparate = DateUtils.secondSeparate(fingerprintAuthenticateErrorLockOutTime);
if (secondSeparate >= Magic.INT_SANSHI) {
authenticateStart = true;
if (sweetAlertDialogFingerprintAuthenticate == null) {
// 开始执行
sweetAlertDialogFingerprintAuthenticate = baseHintDialogCustomImageCreate(this, getString(R.string.fingerprintAuthenticate),
getString(R.string.fingerprintAuthenticateHint), getString(R.string.cancel), R.drawable.ic_fingerprint, false, this);
} else {
restoreDialog();
}
fingerprintAuthenticate.execute(this, cancellationSignal, sweetAlertDialogFingerprintAuthenticate);
// 重置
App.getInstance().setFingerprintAuthenticateNumber(5);
SharedPrefUtils.saveString(this, Flag.FINGERPRINT_AUTHENTICATE_ERROR_LOCK_OUT_DATE, null);
} else {
toastShort((30 - secondSeparate) + getString(R.string.fingerprintAuthenticateRetryHint));
}
} else {
// 开始执行
if (sweetAlertDialogFingerprintAuthenticate == null) {
authenticateStart = true;
sweetAlertDialogFingerprintAuthenticate = baseHintDialogCustomImageCreate(this, getString(R.string.fingerprintAuthenticate),
getString(R.string.fingerprintAuthenticateHint), getString(R.string.cancel), R.drawable.ic_fingerprint, false, this);
fingerprintAuthenticate.execute(this, cancellationSignal, sweetAlertDialogFingerprintAuthenticate);
} else {
authenticateStart = true;
restoreDialog();
sweetAlertDialogFingerprintAuthenticate.show();
}
}
} else {
baseHintDialogCustomImageCreate(this, getString(R.string.FingerprintAuthenticateOpenHintTwo), getString(R.string.FingerprintAuthenticateOpenContent),
getString(R.string.know), R.drawable.ic_sad, false, this).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 指纹认证成功或没成功
if (sweetAlertDialogFingerprintAuthenticate != null) {
sweetAlertDialogFingerprintAuthenticate = null;
}
AuthenticateHandle.fingerprintAuthenticateCancel(cancellationSignal);
}
@Override
public void dialogDismiss(int flag) {
switch (flag) {
case 1:
if (authenticateStart) {
// 指纹认证
if (sweetAlertDialogFingerprintAuthenticate != null && sweetAlertDialogFingerprintAuthenticate.isShowing()) {
authenticateStart = false;
sweetAlertDialogFingerprintAuthenticate.dismiss();
}
} else {
// 登录、未开启指纹认证
baseHintDialogDestroy();
}
break;
default:
break;
}
}
复制代码
注意
一
fingerprintManagerCompat.authenticate(
cryptoObjectHelper.buildCryptoObject(),
0,
cancellationSignal,
new AuthenticateCallback(HandleCooperate.stepHandle(sweetAlertDialog)),
null);
复制代码
上调开始一次指纹认证流程,通30秒(APP重启接上时至30秒复原)。结点:
- 30秒内认证成功
- 30秒内5次认证失败致错误(指纹已禁用,30秒后再试)
- 30秒内无操作
二
一流程一fingerprintManagerCompat
实例,不可多流程复用一实例(无效)。
示例
android-FingerprintDialog android-AsymmetricFingerprintDialog soter