最近在做指纹和刷脸的校验,在开发过程中遇到了一些问题,这里记录一下。
一、调研结论
在对人脸识别和指纹识别进行了调研之后,我们得出的结论是在实际开发中,Android系统提供的API无法具体区分用户录入了哪种生物信息,只能判断用户是否录入了生物信息以及生物信息的强弱,在调用生物信息校验的时候,系统会根据实际情况自行展示人脸或者指纹;根据目前的情况来看,开发者只能优先调用强生物信息校验,如果没有强生物信息,再调用弱生物信息进行校验。
二、开发盲点
1:系统对于强生物信息和弱生物信息的界定并非一成不变的指定为某种生物信息,而是根据设备的情况来判断,比如有的设备录入的是3D人脸信息,那人脸就可能是强生物信息,有的设备录入的是2D人脸信息,那人脸就可能是弱生物信息
2:根据我们的调研发现,设备同时开启指纹和人脸的情况下,启动强生物信息校验时只会启动指纹;启动弱生物信息校验时部分设备会同时启动指纹和人脸,部分设备只有指纹,没有设备会只有人脸。
三、具体调研的设备情况如下
设备中指纹和人脸都有的情况下:
oppo(安卓13): 启动strong,只有指纹;启动week,指纹和人脸都有
魅族: 启动strong,只有指纹;启动week,只有指纹
华为: 启动strong,只有指纹;启动week,只有指纹
荣耀(安卓7): 启动strong,只有指纹;启动week,不支持人脸 —— 该设备只支持指纹
华为(安卓12): 启动strong,只有指纹;启动week,只有指纹
四、根据以上的调研,我们决定只做指纹的校验,具体的代码如下
<!--指纹支付和刷脸支付的权限-->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
import android.content.Intent
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.provider.Settings
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.kongfz.app.core.utils.ToastUtils
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
//生物识别工具类,包含指纹和人脸
//一:原生 BiometricPrompt是 Android 9.0(API 28)中官方引入的系统 API,直接集成在系统框架中,仅在 API 28 及以上版本的原生系统中可用。
//二:AndroidX 中的 BiometricPrompt:
//为了让低版本设备(如 API 23+)也能使用统一的生物识别接口,Google 在 AndroidX 库(androidx.biometric:biometric)中提供了 BiometricPrompt 的兼容实现。
//这个兼容库通过 包装低版本系统的 FingerprintManager,在 API 23+ 设备上模拟出 BiometricPrompt 的接口和功能,包括指纹识别。
//本类使用AndroidX 中的 BiometricPrompt
class BiometricIdentificationUtils(private val context: AppCompatActivity) {
// 密钥存储相关
private lateinit var keyStore: KeyStore
private lateinit var keyGenerator: KeyGenerator
private var cipher: Cipher? = null
private val keyName = "biometric_identification_key"
// 生物识别相关
private lateinit var biometricPrompt: BiometricPrompt
private var authCallback: AuthCallback? = null
// 回调接口
interface AuthCallback {
fun onSuccess()
fun onFailed()
fun onError(errorCode: Int, errorMsg: String)
}
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //API版本大于23就进行秘钥设置
initKeyStore()
initCipher()
}
initBiometricPrompt()
}
//初始化BiometricPrompt
private fun initBiometricPrompt() {
val executor = ContextCompat.getMainExecutor(context)
biometricPrompt = BiometricPrompt(context, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
//华为:errorCode = 5 取消指纹 errorCode = 7 操作过于频繁,请稍后再试
//小米:errorCode = 13 取消指纹 errorCode = 7 尝试次数过多,请稍后重试。
//oppo:errorCode = 13 取消指纹 errorCode = 7 尝试次数过多,请稍后重试。
//vivo:errorCode = 13 取消指纹 errorCode = 7 尝试次数过多,请稍后重试。
//BIOMETRIC_ERROR_LOCKOUT:7
authCallback?.onError(errorCode, errString.toString())
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
authCallback?.onSuccess()
}
override fun onAuthenticationFailed() {
authCallback?.onFailed()
}
})
}
//检查设备是否支持指纹识别 支持:true 不支持:false
fun isFingerprintSupported(): Boolean {
// < 23
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false
}
// >= 23 && <29
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
val fingerprintManager = context.getSystemService(FingerprintManager::class.java)
return fingerprintManager?.isHardwareDetected ?: false //已验证不为null
}
// >= 29
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val biometricManager = BiometricManager.from(context)
val isHwUnavailable =
biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE //硬件不可用
val isNoHw =
biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE //没有硬件
return !isHwUnavailable && !isNoHw
}
return false
}
//检查是否已设置指纹 设置:true 没设置:false
fun hasEnrolledFingerprints(): Boolean {
// < 23
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false
}
// >= 23 && <29
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
val fingerprintManager = context.getSystemService(FingerprintManager::class.java)
return fingerprintManager?.hasEnrolledFingerprints() ?: false
}
// >= 29
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return BiometricManager.from(context)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS
}
return false
}
//创建加密对象
private fun createCryptoObject(): BiometricPrompt.CryptoObject? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && initCipher()) {
cipher?.let { BiometricPrompt.CryptoObject(it) }
} else {
null
}
}
//初始化密钥存储
@RequiresApi(Build.VERSION_CODES.M)
private fun initKeyStore() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
)
keyGenerator.init(
KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build()
)
keyGenerator.generateKey()
} catch (e: Exception) {
e.printStackTrace()
}
}
//初始化加密器
@RequiresApi(Build.VERSION_CODES.M)
private fun initCipher(): Boolean {
return try {
cipher = Cipher.getInstance(
"${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}"
)
keyStore.load(null)
val key = keyStore.getKey(keyName, null) as SecretKey
cipher?.init(Cipher.ENCRYPT_MODE, key)
true
} catch (e: KeyPermanentlyInvalidatedException) {
// 密钥失效,重新生成
initKeyStore()
false
} catch (e: Exception) {
e.printStackTrace()
false
}
}
fun canUseFinger(): Boolean {
if (!isFingerprintSupported()) {
ToastUtils.showSafely("设备不支持指纹识别")
return false
}
if (!hasEnrolledFingerprints()) {
ToastUtils.showSafely("请先在系统设置中录入指纹")
return false
}
return true
}
//启动指纹验证,启动前需进行判断是否支持指纹和是否已经设置指纹
fun startAuthentication(authCallback: AuthCallback) {
this.authCallback = authCallback
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("指纹验证")
.setSubtitle("请将手指放在指纹传感器上")
.setNegativeButtonText("取消")
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.build()
// 尝试使用加密方式验证(更高安全性)
val cryptoObject = createCryptoObject()
if (cryptoObject != null) {
biometricPrompt.authenticate(promptInfo, cryptoObject)
} else {
// 加密方式失败时使用普通验证
biometricPrompt.authenticate(promptInfo)
}
}
//取消指纹验证
fun cancelAuthentication() {
biometricPrompt.cancelAuthentication()
}
//跳转到系统的设置指纹的页面
fun jumpToFingerprintEnroll(context: AppCompatActivity) {
try {
// Android 9.0(API 28)及以上,可直接跳转到指纹录入页面
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val intent = Intent()
intent.setAction(Settings.ACTION_FINGERPRINT_ENROLL)
context.startActivity(intent)
}
} catch (e: Exception) {
ToastUtils.showSafely("无法打开指纹设置页面")
}
}
}