'ANDROID - Biometric_Not_Enrolled not working on my test device but works on emulator
my Biometric feature is working nice at emulator but when I test it on my Galaxy A7 2017, it is not working properly, instead of recognize as BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED, it recognizes as BiometricManager.BIOMETRIC_SUCCESS even with no Biometric Configured, I just removed my old biometric that was configured to test it.
import android.content.Context
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
fun createBiometricCallback (
onAuthenticationError: () -> Unit = {},
onAuthenticationFailed: () -> Unit = {},
onAuthenticationSuccess: (result: BiometricPrompt.AuthenticationResult) -> Unit
) = @RequiresApi(Build.VERSION_CODES.P)
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
onAuthenticationError.invoke()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
onAuthenticationSuccess.invoke(result)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onAuthenticationFailed.invoke()
}
}
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.M)
fun canUseBiometricAuthentication(
context: Context,
canUseBiometricSuccess: () -> Unit = {},
canUseBiometricErrorNoHardware: () -> Unit = {},
canUseBiometricErrorHwUnavailable: () -> Unit = {},
canUseBiometricErrorNoneEnrolled: () -> Unit = {},
canUseBiometricErrorSecurityUpdateRequired: () -> Unit = {},
canUseBiometricErrorUnsupported: () -> Unit = {},
canUseBiometricStatusUnknown: () -> Unit = {},
) {
context?.let { context ->
val biometricManager = BiometricManager.from(context)
when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
Log.d("MY_APP_TAG", "App can authenticate using biometrics.")
Toast.makeText(context,"App can authenticate using biometrics.", Toast.LENGTH_LONG).show()
canUseBiometricSuccess.invoke()
}
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
Log.e("MY_APP_TAG", "No biometric features available on this device.")
Toast.makeText(context,"No biometric features available on this device.", Toast.LENGTH_LONG).show()
canUseBiometricErrorNoHardware.invoke()
}
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
Log.e("MY_APP_TAG", "Biometric features are currently unavailable.")
Toast.makeText(context,"Biometric features are currently unavailable.", Toast.LENGTH_LONG).show()
canUseBiometricErrorHwUnavailable.invoke()
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
Log.e("MY_APP_TAG", "Biometric features are not configured.")
Toast.makeText(context,"Biometric features are not configured.", Toast.LENGTH_LONG).show()
canUseBiometricErrorNoneEnrolled.invoke()
}
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
Log.e("MY_APP_TAG", "Security Update Required Error.")
Toast.makeText(context,"Security Update Required Error.", Toast.LENGTH_LONG).show()
canUseBiometricErrorSecurityUpdateRequired.invoke()
}
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
Log.e("MY_APP_TAG", "Error Unsupported.")
Toast.makeText(context,"Error Unsupported.", Toast.LENGTH_LONG).show()
canUseBiometricErrorUnsupported.invoke()
}
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
Log.e("MY_APP_TAG", "Status Unknown.")
Toast.makeText(context,"Status Unknown.", Toast.LENGTH_LONG).show()
canUseBiometricStatusUnknown.invoke()
}
else -> {canUseBiometricStatusUnknown.invoke()}
}
}
}
fun getBiometricPromptBuilder(context: Context) = context.run {
BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric")
.setConfirmationRequired(true)
.setNegativeButtonText("Cancel")
.build()
}
fun createBiometricPrompt(activity: FragmentActivity,context: Context,authCallBack: BiometricPrompt.AuthenticationCallback) =
BiometricPrompt(
activity,
ContextCompat.getMainExecutor(context!!),
authCallBack
)
fun BiometricPrompt.authenticateWithBiometric(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (isKeyAlreadyCreated().not()) {
generateSecretKey()
}
val ivParameterSpec = IvParameterSpec(Base64.decode(BuildConfig.CIPHER_IV, Base64.DEFAULT))
val cipher = getCipher()
val secretKey = getSecretKey()
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
authenticate(
getBiometricPromptBuilder(context),
BiometricPrompt.CryptoObject(cipher)
)
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun generateSecretKey() {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(
KeyGenParameterSpec.Builder(
BuildConfig.KeyStoreAlias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setRandomizedEncryptionRequired(false)
.build()
)
keyGenerator.generateKey()
}
@RequiresApi(Build.VERSION_CODES.M)
fun getSecretKey(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
return keyStore.getKey(BuildConfig.KeyStoreAlias, null) as SecretKey
}
@RequiresApi(Build.VERSION_CODES.M)
fun getCipher(): Cipher {
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
}
@RequiresApi(Build.VERSION_CODES.M)
fun isKeyAlreadyCreated(): Boolean {
return try {
getSecretKey()
true
} catch (e: Exception) {
false
}
}
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.util.Base64
import android.widget.Toast
import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import br.com.px.commons.authenticateWithBiometric
import br.com.px.commons.canUseBiometricAuthentication
import br.com.px.commons.createBiometricCallback
import br.com.px.commons.createBiometricPrompt
import br.com.px.uikit.utils.extensions.*
import exceptions.*
class BiometricViewModel : ViewModel() {
private val _biometricAuthenticationState by viewState<Unit>()
val biometricAuthenticationState = _biometricAuthenticationState.asLiveData()
fun canBiometricHardwareAuthentication(context: Context) {
canUseBiometricAuthentication(
context,
canUseBiometricSuccess = {_biometricAuthenticationState.postSuccess(Unit)},
canUseBiometricErrorNoHardware = {_biometricAuthenticationState.postError(BiometricNoHardwareException())},
canUseBiometricErrorHwUnavailable = {_biometricAuthenticationState.postError(BiometricHwUnavailableException())},
canUseBiometricErrorNoneEnrolled = {_biometricAuthenticationState.postError(BiometricNoneEnrolledException())},
canUseBiometricErrorSecurityUpdateRequired = {_biometricAuthenticationState.postError(BiometricSecurityUpdateRequiredException())},
canUseBiometricErrorUnsupported = {_biometricAuthenticationState.postError(BiometricUnsupportedException())},
canUseBiometricStatusUnknown = {_biometricAuthenticationState.postError(BiometricStatusUnknownException())}
)
}
private fun createAuthenticationCallback(userPassword: String, context: Context) = createBiometricCallback (
onAuthenticationSuccess = { result ->
val encryptedPassword = Base64.encodeToString(
result.cryptoObject?.cipher?.doFinal(
(userPassword ?: "").toByteArray(Charsets.UTF_8)
),
Base64.DEFAULT
)
Toast.makeText(context,"Biometric Authentication Success.",Toast.LENGTH_LONG).show()
Toast.makeText(context,"Encrypted Password: $encryptedPassword",Toast.LENGTH_LONG).show()
},
onAuthenticationError = {
Toast.makeText(context,"Biometric Authentication Errorrrrrr.",Toast.LENGTH_LONG).show()
},
onAuthenticationFailed = {
Toast.makeText(context,"Biometric Authentication Failed.",Toast.LENGTH_LONG).show()
}
)
fun showBiometricPrompt (context: Context, fragmentActivity: FragmentActivity){
createBiometricPrompt(fragmentActivity,context,
createAuthenticationCallback("userPassword",context)
).authenticateWithBiometric(
context = context
)
}
fun openBiometricConfiguration(activity: Activity){
val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
}
ActivityCompat.startActivityForResult(activity, enrollIntent, 0, null)
}
}
Solution 1:[1]
My assumption is that it's working as expected.
You have included or BiometricManager.Authenticators.DEVICE_CREDENTIAL
in your biometricManager.canAuthenticate
call.
It is successful because you have Device Credentials (PIN/Pattern/Password) setup on the device aswell.
If you request only for BiometricManager.Authenticators.BIOMETRIC_STRONG
it will return the BIOMETRIC_ERROR_NONE_ENROLLED
that you're after.
From developer.android.com BiometricManager.canAuthenticate:
For example, if Authenticators#DEVICE_CREDENTIAL | Authenticators#BIOMETRIC_STRONG is queried and only Authenticators#DEVICE_CREDENTIAL is set up, this API will return BIOMETRIC_SUCCESS
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Guy Davis |