一、简述
业务需求,需要指纹登录,鉴于市面上的资料不是特别齐全,走了不少弯路。现在通了,写点东西给大伙做个参考。末尾会提供demo和参考资料
二、指纹登录/支付工作流程
指纹验证加密流程.png
最新的流程图请点击链接
三、原理解析
指纹验证
通过FingerprintManager.authenticate()方法即可验证,同时实现FingerprintManager.AuthenticationCallback即可进行监听。
数据加解密
待加密数据结合Android KeyStore System进行加密,需要经过设备校验后才能解密。(Android M后才有)
指纹验证成功后,回调AuthenticationCallback.onAuthenticationSucceeded(AuthenticationResult result),利用result中的CryptoObject的Cipher进行加密
Cipher cipher = result.getCryptoObject().getCipher();
byte[] encrypted = cipher.doFinal(data.getBytes());
byte[] IV = cipher.getIV();
String se = Base64.encodeToString(encrypted, Base64.URL_SAFE);
String siv = Base64.encodeToString(IV, Base64.URL_SAFE);
获得加密的数据和iv后,本地保存起来。需要用的时候,再根据SecretKey、iv初始化出cipher,进行解密cipher.doFinal(byte[] input)
为什么这样安全? --> 因为Cipher根据SecretKey和IV初始化出来,而SecretKey由KeyStore保存在Android系统中,IV由指纹验证自动生成。由这两者生成出来的Cipher进行加解密,即可保证安全。
四、实现过程(以指纹登录为例,指纹支付也大同小异)
判断当前设备是否支持指纹(具体详细代码,demo有)
/**
* 获取当前设备的指纹状态
*
* @param ctx
* @return 是否支持指纹
*/
public FingerPrintInitType getFingerprintAvailable(Context ctx) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return FingerPrintInitType.NOT_SUPPORT;
} else if (!isKeyProtectedEnforcedBySecureHardware()) {
return FingerPrintInitType.NO_FINGER_HARDWARE;
} else if (!manager.isHardwareDetected()) {
Toast.makeText(ctx, "该设备尚未检测到指纹硬件", Toast.LENGTH_SHORT).show();
return FingerPrintInitType.NO_FINGER_HARDWARE;
} else if (!manager.hasEnrolledFingerprints()) {
Toast.makeText(ctx, "该设备未录入指纹,请去系统->设置中添加指纹", Toast.LENGTH_SHORT).show();
return FingerPrintInitType.NONE_FINGER;
}
return FingerPrintInitType.HAS_FINGER;
}
如果当前设备支持指纹,则显示是否开启指纹登录。
点击开启指纹登录后,则弹出指纹验证UI,并将一些关键数据EncryptData进行加密(如账号、密码/openToken)
/**
* 指纹验证关键方法
*
* @param intPurpose 加密/解密 KeyProperties.PURPOSE_ENCRYPT/ KeyProperties.PURPOSE_DECRYPT
* @param encryptData 加密数据,加密的时候才需要填
*/
public boolean authenticate(int intPurpose, String encryptData) {
try {
purpose = intPurpose;
FingerprintManager.CryptoObject object;
if (purpose == KeyProperties.PURPOSE_DECRYPT) {
//解密
String IV = mLocalSharedPreference.getData(keyAlias + mLocalSharedPreference.IVKeyName);
object = mLocalAndroidKeyStore.getCryptoObject(keyAlias, Cipher.DECRYPT_MODE, Base64.decode(IV, Base64.URL_SAFE));
if (object == null) {
return false;
}
} else {
//加密
generateKey();
data = encryptData;
object = mLocalAndroidKeyStore.getCryptoObject(keyAlias, Cipher.ENCRYPT_MODE, null);
}
mCancellationSignal = new CancellationSignal();
manager.authenticate(object, mCancellationSignal, 0, this, null);
return true;
} catch (SecurityException e) {
e.printStackTrace();
return false;
}
}
指纹验证后,会回调FingerprintManager.AuthenticationCallback接口,在这个接口里面进行数据的加密和解密。指纹验证成功会调用public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result),指纹验证失败会调用public void onAuthenticationFailed(),指纹验证失败次数过多会调用public void onAuthenticationError(int errorCode, CharSequence errString)
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
if (callback == null) {
return;
}
if (result.getCryptoObject() == null) {
callback.onAuthenticationFail();
return;
}
final Cipher cipher = result.getCryptoObject().getCipher();
if (purpose == KeyProperties.PURPOSE_DECRYPT) {
//取出secret key并返回
String data = mLocalSharedPreference.getData(keyAlias + mLocalSharedPreference.dataKeyName);
if (TextUtils.isEmpty(data)) {
callback.onAuthenticationFail();
return;
}
try {
byte[] decrypted = cipher.doFinal(Base64.decode(data, Base64.URL_SAFE));
callback.onAuthenticationSucceeded(new String(decrypted));
} catch (BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
callback.onAuthenticationFail();
}
} else {
//将前面生成的data包装成secret key,存入沙盒
try {
byte[] encrypted = cipher.doFinal(data.getBytes());
byte[] IV = cipher.getIV();
String se = Base64.encodeToString(encrypted, Base64.URL_SAFE);
String siv = Base64.encodeToString(IV, Base64.URL_SAFE);
if (mLocalSharedPreference.storeData(keyAlias + mLocalSharedPreference.dataKeyName, se) &&
mLocalSharedPreference.storeData(keyAlias + mLocalSharedPreference.IVKeyName, siv)) {
callback.onAuthenticationSucceeded(se);
} else {
callback.onAuthenticationFail();
}
} catch (BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
callback.onAuthenticationFail();
}
}
}
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
// LogUtils.d(errorCode + " " + errString);
if (callback != null) {
callback.onAuthenticationError();
}
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
}
@Override
public void onAuthenticationFailed() {
if (callback != null) {
callback.onAuthenticationFail();
}
}
备注
指纹发生变更也会导致指纹验证失败
指纹验证5次后,30s内再次申请指纹验证会直接失败
Android 6.0(Android M)之后才有统一的指纹验证接口,建议说服产品只适配6.0及之后的设备,之前的放弃
参考资料