最近在做指紋和刷臉的校驗,在開發(fā)過程中遇到了一些問題,這里記錄一下。
一、調(diào)研結論
在對人臉識別和指紋識別進行了調(diào)研之后,我們得出的結論是在實際開發(fā)中,Android系統(tǒng)提供的API無法具體區(qū)分用戶錄入了哪種生物信息,只能判斷用戶是否錄入了生物信息以及生物信息的強弱,在調(diào)用生物信息校驗的時候,系統(tǒng)會根據(jù)實際情況自行展示人臉或者指紋;根據(jù)目前的情況來看,開發(fā)者只能優(yōu)先調(diào)用強生物信息校驗,如果沒有強生物信息,再調(diào)用弱生物信息進行校驗。
二、開發(fā)盲點
1:系統(tǒng)對于強生物信息和弱生物信息的界定并非一成不變的指定為某種生物信息,而是根據(jù)設備的情況來判斷,比如有的設備錄入的是3D人臉信息,那人臉就可能是強生物信息,有的設備錄入的是2D人臉信息,那人臉就可能是弱生物信息
2:根據(jù)我們的調(diào)研發(fā)現(xiàn),設備同時開啟指紋和人臉的情況下,啟動強生物信息校驗時只會啟動指紋;啟動弱生物信息校驗時部分設備會同時啟動指紋和人臉,部分設備只有指紋,沒有設備會只有人臉。
三、具體調(diào)研的設備情況如下
設備中指紋和人臉都有的情況下:
oppo(安卓13): 啟動strong,只有指紋;啟動week,指紋和人臉都有
魅族: 啟動strong,只有指紋;啟動week,只有指紋
華為: 啟動strong,只有指紋;啟動week,只有指紋
榮耀(安卓7): 啟動strong,只有指紋;啟動week,不支持人臉 —— 該設備只支持指紋
華為(安卓12): 啟動strong,只有指紋;啟動week,只有指紋
四、根據(jù)以上的調(diào)研,我們決定只做指紋的校驗,具體的代碼如下
<!--指紋支付和刷臉支付的權限-->
<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)中官方引入的系統(tǒng) API,直接集成在系統(tǒng)框架中,僅在 API 28 及以上版本的原生系統(tǒng)中可用。
//二:AndroidX 中的 BiometricPrompt:
//為了讓低版本設備(如 API 23+)也能使用統(tǒng)一的生物識別接口,Google 在 AndroidX 庫(androidx.biometric:biometric)中提供了 BiometricPrompt 的兼容實現(xiàn)。
//這個兼容庫通過 包裝低版本系統(tǒng)的 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
// 回調(diào)接口
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 嘗試次數(shù)過多,請稍后重試。
//oppo:errorCode = 13 取消指紋 errorCode = 7 嘗試次數(shù)過多,請稍后重試。
//vivo:errorCode = 13 取消指紋 errorCode = 7 嘗試次數(shù)過多,請稍后重試。
//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
}
//創(chuàng)建加密對象
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("請先在系統(tǒng)設置中錄入指紋")
return false
}
return true
}
//啟動指紋驗證,啟動前需進行判斷是否支持指紋和是否已經(jīng)設置指紋
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()
}
//跳轉(zhuǎn)到系統(tǒng)的設置指紋的頁面
fun jumpToFingerprintEnroll(context: AppCompatActivity) {
try {
// Android 9.0(API 28)及以上,可直接跳轉(zhuǎn)到指紋錄入頁面
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("無法打開指紋設置頁面")
}
}
}