Upgrade to Pro — share decks privately, control downloads, hide ads and more …

No Two Biometrics (APIs) Are Alike // droidcon ...

No Two Biometrics (APIs) Are Alike // droidcon Lisbon 2025

My presentation about the Android Biometric API, KeyStore and Key Attestation as presented at droidcon Lisbon 2025 on September 5, 2025.

Intro
Do you need to protect some functionality or store sensitive data in your Android app? Biometric authentication seems both convenient and secure at face value. However under the skin of standardized APIs like androidx.biometric lies a web of strange edge cases and security vulnerabilities caused by API misuse in many cases.

Based on extensive research and development of security-sensitive apps, we will discuss the quirks and features of Biometrics API and the Keystore system of Android. Among many others, you will learn:

- How Biometric authentication can be bypassed without proper usage of the CryptoObject.
- Which are the obvious (and not so obvious) usability implications of making Android invalidate keys when new (biometric) credentials are added?
- How do we require user authentication for accessing keys and what it means? Also, what does the timeout parameter mean in this case?
- How to use hardware-backed key storage, Strongbox, and how to verify them via key attestation.

Links
Open source library to wrap different biometric APIs (only use if you can trust it):
https://github.com/sergeykomlach/AdvancedBiometricPromptCompat

Jetpack Biometric Library:
https://developer.android.com/jetpack/androidx/releases/biometric

Blogpost announcing Biometric API changes (including the introduction of Biometric Classes) in Android 11:
https://security.googleblog.com/2020/09/lockscreen-and-authentication.html

Official Android documentation about showing a BiometricPrompt and authenticating with it:
https://developer.android.com/identity/sign-in/biometric-auth

My sample app to check the secure storage capabilities of a device:
https://github.com/balazsgerlei/AndroidSecureStorageCapabilitiesInspector

Android Device Security Database research project listing some devices that has a StrongBox Secure Element:
https://www.android-device-security.org/database/?realMeasurementsOnly=true&show=Strongbox&sortBy=COUNT%20Lab%20Strongbox%20True

Official Android documentation about Key Attestation
https://developer.android.com/privacy-and-security/security-key-attestation

Google's Key Attestation implementation:
https://github.com/android/keyattestation

We meant to present it together with my colleague but things turned out like I presented it alone.

Avatar for Balázs Gerlei

Balázs Gerlei

September 05, 2025
Tweet

More Decks by Balázs Gerlei

Other Decks in Programming

Transcript

  1. No Two Biometrics (APIs) Are Alike Balázs Gerlei Josu Vergara

    Lecue Expert Software Engineer Senior Software Engineer
  2. Intro 1. Can two people have the same fingerprint? 2.

    If my phone has biometric unlock can it be used in any app? 3. If I use the androidx.biometric support library, can I use the same implementation on all devices?
  3. • Key Pair based Protocol FIDO UAF Create Key Pair

    Client-side Server-side Public Key User Authenticates Sign Data Store Public Key Validate Signature Private Key Public Key Signed Content Public Key
  4. The Choice Between Libraries • System frameworks: ◦ android.hardware.fingerprint.FingerprintManager -

    deprecated in Android 9 (API 28) ◦ android.hardware.biometrics.BiometricPrompt - introduced in Android 9 (API 28) • Jetpack androidx.biometric library • Vendor specific libraries (Samsung, Huawei, etc.) • FOSS library to wrap everything (caution advised): ◦ github.com/sergeykomlach/AdvancedBiometricPromptCompat
  5. FingerprintManager • Only fingerprint sensor • Doesn’t handle the UI

    (custom code needed) • Android 6+ (API 23) • All the different sensors: Face, Iris, Fingerprint… • Handles UI ◦ Displaying the prompt ◦ Reporting errors • Only Android 9+ (API 28) BiometricPrompt
  6. Jetpack Biometric Library: A Holy Grail? • Built on top

    of FingerprintManager and BiometricPrompt • Advantages: ◦ Supports different type of sensors ◦ Provides a unified UI - even pre-Android 9 (API 28) ◦ Backward compatible • However: ◦ Last stable release from 2021 ◦ Can be buggy ◦ Actively changing - just added new API similar to ActivityResults developer.android.com/jetpack/androidx/releases/biometric
  7. Fingerprint Face Iris • Check availability: packageManager.hasSystemFeature(PackageManager.FEATURE_FACE) • Even if

    a sensor is available, it’s not necessarily usable (in apps) Type of Biometric Sensors on Android
  8. Iris Sensors • Briefly offered by Samsung: ◦ Only usable

    in apps on Galaxy S9, S9+ and Note 9 ◦ (S8 and Note 8 also had the sensor but no Biometric API) • All of these devices return false to packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS) • Instead need to query a custom metadata (if it doesn’t throw an Exception): packageManager.getPackageInfo("com.samsung.android.server.iris", PackageManager.GET_META_DATA)
  9. Face Sensors • Users unlocking their phone with face don’t

    understand why they cannot authenticate with it in an app ◦ They blame the app - though it’s often out of the developer’s control ◦ You can allow less-precise (Face) sensors if you are willing to sacrifice on security ▪ Users are unlikely to understand the trade-off
  10. Face Sensors • Only Samsung seem to support less-secure Face

    auth in apps ◦ May be the only available sensor (i.e. tablets) ◦ In case of multiple sensors, users can choose between them • Pixel 4 & 4 XL had infrared-based Face sensor ◦ Only available sensor on these devices • Newer Pixels (since Pixel 8) also support Face auth in apps ◦ Still RGB camera-based but with AI “magic” ◦ They also have (under display) fingerprint sensors ◦ Face authentication is a bypass
  11. Biometric Classes • A Class 3⃣ sensor can also act

    as Class 2⃣ • All fingerprint sensors seem to be Class 3⃣ • If there are multiple sensors, it cannot be determined which one falls to which Class • Even if a device has Face Unlock, it may not even be Class 2⃣ ◦ Meaning it cannot be used in apps (e.g. Pixel 7)
  12. Biometric (?) Authenticators • Authenticators are grouped to: ◦ BiometricManager.Authenticators.BIOMETRIC_WEAK

    (Class 2⃣) ◦ BiometricManager.Authenticators.BIOMETRIC_STRONG (Class 3⃣) ◦ BiometricManager.Authenticators.DEVICE_CREDENTIAL (Class❓) - only Android 11+ (API 30) • You can set what you allow, but you cannot force using one
  13. BiometricPrompt - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login

    for my app") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) } } ) biometricPrompt.authenticate(promptInfo)
  14. BiometricPrompt - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login

    for my app") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) } } ) biometricPrompt.authenticate(promptInfo)
  15. BiometricPrompt - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login

    for my app") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) } } ) biometricPrompt.authenticate(promptInfo)
  16. BiometricPrompt - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login

    for my app") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) } } ) biometricPrompt.authenticate(promptInfo)
  17. BiometricPrompt - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login

    for my app") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) } } ) biometricPrompt.authenticate(promptInfo)
  18. BiometricPrompt and Key Protection • Android allows to protect keys

    with authentication, using KeyGenParameterSpec.Builder.setUserAuthenticationRequired • Via CryptoObject from the Biometrics API ◦ A handle for the key material (Signature or Cipher) ◦ Only available with Class 3⃣ sensors ▪ A protected key cannot be accessed when the user authenticates with a Class 2⃣ sensor developer.android.com/identity/sign-in/biometric-auth
  19. BiometricPrompt and Key Protection - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo =

    BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) // Verify that result.cryptoobject is the same as the one provided to the prompt } } ) biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
  20. BiometricPrompt and Key Protection - Example developer.android.com/identity/sign-in/biometric-auth val promptInfo =

    BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK) .setNegativeButtonText("Cancel") .build() val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) // Verify that result.cryptoobject is the same as the one provided to the prompt } } ) biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
  21. BiometricPrompt - Allowing Class 2⃣ • Use this with care:

    ◦ The sensor itself is less secure ◦ Can’t be used to protect credentials with user authentication developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators( BIOMETRIC_WEAK ) // This includes BIOMETRIC_STRONG .setNegativeButtonText("Cancel") .build()
  22. BiometricPrompt - Allowing Class 2⃣ • Use this with care:

    ◦ The sensor itself is less secure ◦ Can’t be used to protect credentials with user authentication developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators( BIOMETRIC_WEAK ) // This includes BIOMETRIC_STRONG .setNegativeButtonText("Cancel") .build()
  23. BiometricPrompt - Allowing Class 2⃣ • Use this with care:

    ◦ The sensor itself is less secure ◦ Can’t be used to protect credentials with user authentication developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators( BIOMETRIC_WEAK ) // This includes BIOMETRIC_STRONG .setNegativeButtonText("Cancel") .build()
  24. BiometricPrompt - Device Passcode Fallback • Device Credentials can be

    allowed as a fallback ◦ Android 11+ (API 30) developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators( DEVICE_CREDENTIAL ) // No biometrics involved .build() ◦ Users can choose to authenticate with PIN, Password or Pattern via a button in place of Cancel ▪ That’s why negative button text cannot be set if fallback is allowed
  25. BiometricPrompt - Device Passcode Fallback • Device Credentials can be

    allowed as a fallback ◦ Android 11+ (API 30) developer.android.com/identity/sign-in/biometric-auth val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Please authenticate") .setAllowedAuthenticators( DEVICE_CREDENTIAL ) // No biometrics involved .build() ◦ Users can choose to authenticate with PIN, Password or Pattern via a button in place of Cancel ▪ That’s why negative button text cannot be set if fallback is allowed
  26. BiometricPrompt - Some Quirks • Difficult to demo with physical

    devices • The UIs lifecycle is coupled with the Activity / Fragment: ◦ Difficult to know whether cancellation happened because the application has been backgrounded developer.android.com/identity/sign-in/biometric-auth
  27. Protecting Keys with Biometrics • FIDO UAF relies on public

    key cryptography ◦ Transactions and authentication requests needs to be signed with the private key • The best is to protect these keys with Biometric authentication ◦ Requiring user authentication for each key access if possible • Considerations are basically the same even if you “only” protect some local data
  28. Protecting Keys with Biometrics • Biometric authentication is (only) access

    control, managed by Android ◦ Keys are in no way connected to biometric data ◦ Keys can be invalidated when a new (biometric) credential is added on Android 7+ (API 24) ◦ Or you can allow fallback to device credentials (Pin, Pattern or Password) These two cannot be set together
  29. Protecting Keys with Biometrics • A timeout can be set

    during which key access doesn’t require re-authentication ◦ Only applicable if you require user authentication ◦ A new prompt can still be shown, but be aware that after even just a device unlock the keys are accessible via code (e.g. using ) ◦ On some (older) Xiaomi devices requiring user authentication for key access doesn’t work without a timeout :(
  30. Secure Key Storage • Trusted Execution Environment (TEE) - Android

    6+ (API 23) ◦ It’s a Secure Element, meaning a completely separate hardware (CPU, memory, storage, true random generator…) and OS ◦ Only communicates with Android via messages • Keys stored in these cannot be retrieved even on rooted phones ◦ But they may be usable (e.g. if the malicious app can impersonate the target app) ◦ It’s a secure (virtual) environment (separate OS), running on the same processor as the main OS • StrongBox - Android 9+ (API 28)
  31. Not all StrongBoxes created equal • Google provides a default

    implementation but OEMs often roll their own - these sometimes have bugs: ◦ Samsung devices with Exynos chips cannot generate keys with SHA-512 signature ◦ Some Xiaomi devices (e.g. Xiaomi 14) cannot sign byte arrays longer than 256 bytes if user authentication is required • Requiring StrongBox severely limits compatibility ◦ Hard to find a list of devices and it is seldom mentioned in specs ◦ Probably only possible to use it in best effort mode
  32. Verify StrongBox support • Can be checked via PackageManager: packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)

    • Can’t be sure without trying to create a key and then check whether it is stored in StrongBox: keyInfo=.securityLevel == KeyProperties.SECURITY_LEVEL_STRONGBOX
  33. SecureStorageCapabilitiesInspector App • Test app that shows secure storage capabilities

    of the device ◦ github.com/balazsgerlei/AndroidSecureStorageCapabilitiesInspector
  34. Devices We Found to Support StrongBox • We’ve found the

    following device that support StrongBox (be aware of limitations): ◦ Google Pixels, starting with Pixel 3 ◦ Samsung Galaxy S S20 and newer, Note 20 and newer ◦ Xiaomi 14 and 15 ◦ OnePlus 12 and 13 ◦ Motorola ThinkPhone, Edge 30 Ultra and Fusion, Edge 50 Ultra ◦ Solana Saga, Seeker • An (also) incomplete list of devices can be found here: ◦ android-device-security.org/database/?realMeasurementsOnly=true &show=Strongbox&sortBy=COUNT%20Lab%20Strongbox%20True
  35. Key Attestation Key attestation gives you more confidence that the

    keys you use in your app are stored in a device's hardware-backed keystore. Key attestation aims to provide a way to strongly determine if an asymmetric key pair is hardware-backed, what the properties of the key are, and what constraints are applied to its usage. developer.android.com/privacy-and-security/security-key-attestation
  36. Key Attestation - Key Creation KeyGenParameterSpec.Builder .setAttestationChallenge Public + Private

    Key Intermediate Certificates Certificate with Public Key Google Root Certificate Key Pair Certificate (Chain) X.509 Extensions developer.android.com/privacy-and-security/security-key-attestation AndroidKeystoreAttestation VerificationAttributes keyStore .getCertificateChain
  37. Key Attestation - Validation • Verify Certificate Chain Integrity •

    Verify Root Certificate is a Google Root Certificate • Checks using the information in the certificate extension: ◦ Boot state is verified ◦ The device bootloader is (un)locked ◦ The Keymaster version ◦ The Keymaster security level: SOFTWARE, TRUSTED_ENVIRONMENT or STRONGBOX ◦ Verify developer certificate signature ◦ Verify package name of the application developer.android.com/privacy-and-security/security-key-attestation
  38. Key Attestation - Validation Result: What It Means • The

    key is generated in a Google certified chipset • Type of key storage (TEE, StrongBox) • The key is unique • Non Rooted Device (?) developer.android.com/privacy-and-security/security-key-attestation
  39. Key Attestation - Validation Result: What It Means • The

    key is generated in a Google certified chipset • Type of key storage (TEE, StrongBox) • The key is unique • Non Rooted Device (?) ◦ Not necessarily (can be spoofed) developer.android.com/privacy-and-security/security-key-attestation
  40. Key Attestation - Validation Result: What It Means • If

    a device fails attestation, it probably either: ◦ Launched with Android 7.0 or older (and doesn't support hardware attestation) ▪ In this case, Android has a software implementation of attestation, producing the same sort of attestation certificate, but signed with a key hardcoded in Android source code. ◦ Isn't a Google Play certified device ▪ In that case, the device maker is free to create their own root and to make whatever claims they like about what the attestation means. developer.android.com/privacy-and-security/security-key-attestation
  41. Key Attestation - Limitations / Issues • This is not

    Application Attestation: ◦ Doesn’t guarantee that the client is a published app in the Play Store • A “snapshot” of the device state ◦ At the time of the key creation • Not all devices support Key Attestation developer.android.com/privacy-and-security/security-key-attestation
  42. Android Key Attestation Verifier Library • Google has a Kotlin

    library for implementing Key Attestation: ◦ github.com/android/keyattestation
  43. Takeaways • Choose the library that works best for you

    • Don’t assume all devices work the same ◦ Be aware of implementation differences (Biometrics API, StrongBox, etc.) • Make sure to use a CryptoObject with the BiometricPrompt ◦ Validate the returned CryptoObject and actually use it to encrypt/decrypt or sign some data • Require user authentication for each key access (if possible) • Use Key Attestation to verify secure key storage server-side
  44. Answers 1. Can two people have the same fingerprint? ◦

    Yes, there are cases of different people with the same fingerprint 2. If my phone has biometric unlock can it be used in any app? ◦ Not necessarily (e.g. Class 1⃣ and 2⃣ Face Unlock) 3. If I use the androidx.biometric support library, can I use the same code on all devices? ◦ At the very least you need to be aware of the differences