Android Biometric Authentication Bypass
Android Biometric Authentication Bypass
Description
Android provides mechanisms to enforce biometric authentication to protect sensitive information. Biometric authentication has evolved over time to provide improved user experience, developer experience and improved security.
Previous implementation using FingerprintManager
is deprecated and must not be used. Proper implementation must use
BiometricManager
with BiometricPrompt
and CryptoObject
.
CryptoObject
provides cryptographic primitives for encryption, decryption and signature validation.
In the example below, calling the authenticate
method without cryptoObject
is vulnerable to authentication bypass:
fun showBiometricPrompt(
title: String = "Biometric Authentication",
subtitle: String = "Enter biometric credentials to proceed.",
description: String = "Input your Fingerprint or FaceID to ensure it's you!",
activity: AppCompatActivity,
listener: BiometricAuthListener,
cryptoObject: BiometricPrompt.CryptoObject? = null,
allowDeviceCredential: Boolean = false
) {
// 1
val promptInfo = setBiometricPromptInfo(
title,
subtitle,
description,
allowDeviceCredential
)
// 2
val biometricPrompt = initBiometricPrompt(activity, listener)
// 3
biometricPrompt.apply {
if (cryptoObject == null) authenticate(promptInfo)
else authenticate(promptInfo, cryptoObject)
}
}
Recommendation
Implement biometric authentication with CryptoObject
usage.
The authentication flow would be as follows when using CryptoObject
:
- The app creates a key in the KeyStore with:
setUserAuthenticationRequired
set totrue
setInvalidatedByBiometricEnrollment
set totrue
setUserAuthenticationValidityDurationSeconds
set to-1
.
val paramsBuilder = KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_SIGN)
paramsBuilder.apply {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
setDigests(KeyProperties.DIGEST_SHA256)
setUserAuthenticationRequired(true)
setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) // ECDSA parameter (P-256) curve
setInvalidatedByBiometricEnrollment(true)
setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> {
setDigests(KeyProperties.DIGEST_SHA256)
setUserAuthenticationRequired(true)
setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
setInvalidatedByBiometricEnrollment(true)
setUserAuthenticationValidityDurationSeconds(-1)
}
else -> {
setDigests(KeyProperties.DIGEST_SHA256)
setUserAuthenticationRequired(true)
setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
setUserAuthenticationValidityDurationSeconds(-1)
}
}
}
-
The keystore key must be used to encrypt information that is authenticating the user, like session information or authentication token.
-
Biometrics are presented before the key is accessed from the KeyStore to decrypt the data. The biometric is validated with
authenticate
method and theCryptoObject
. This solution cannot be bypassed, even on rooted devices as the keystore key can only be used after successful biometric authentication.
fun showBiometricPrompt(
title: String = "Biometric Authentication",
subtitle: String = "Enter biometric credentials to proceed.",
description: String = "Input your Fingerprint or FaceID\nto ensure it's you!",
activity: AppCompatActivity,
listener: BiometricAuthListener
) {
val promptInfo = setBiometricPromptInfo(title, subtitle, description)
val biometricPrompt = initBiometricPrompt(activity, listener)
biometricPrompt.authenticate(
promptInfo, BiometricPrompt.CryptoObject(
CryptoUtil.getOrCreateSignature()
)
)
}
- If
CryptoObject
is not used as part of the authenticate method, it can be bypassed by using dynamic instrumentation with a debugger or with tools like Frida.
Links
- Bypass Biometric Authentication
- Using BiometricPrompt with CryptoObject: How and Why
- Android Biometric API: Getting Started
Standards
- OWASP_MASVS_L2:
- MSTG_AUTH_8