πŸ” Android Security and Privacy

Biometric Authentication with Android

Fingerprint and face unlock make sensitive actions both safer and faster. But a biometric prompt that just returns 'success' is weaker than most developers assume. This guide shows how to use BiometricPrompt correctly, including Keystore-bound authentication for real security.

✍️ Yunok Tech πŸ“… Updated ⏱ 3 min read 🎯 Intermediate
On this page
  1. Two levels of biometric security
  2. Setup and availability
  3. Crypto-bound authentication (recommended)
  4. Server-verified biometric confirmation
  5. UX guidelines
  6. Use cases
  7. Frequently asked questions
  8. Conclusion

Biometrics on Android don't send fingerprints or face data to your app. The system verifies the user and tells your app the result. How you use that result determines security: as a UI gate, or as the key that unlocks cryptographic operations.

Two levels of biometric security

Biometric gate versus crypto-bound authentication Level one: BiometricPrompt returns success and the app shows the content; on a compromised device this boolean can be bypassed. Level two: BiometricPrompt authorises a CryptoObject wrapping a Keystore key that requires user authentication; only after successful authentication can the key decrypt data or sign a server challenge, so bypassing the UI does not unlock the key. Level 1: UI gate BiometricPrompt success show content bypassable on compromised devices Level 2: crypto-bound BiometricPrompt+ CryptoObject unlocks Keystore keyauth required decrypt token /sign server challenge
For anything valuable, bind authentication to a cryptographic operation that the server or the data itself depends on.

Setup and availability

app/build.gradle.ktsGradle KTS
dependencies { implementation("androidx.biometric:biometric:<version>") }
Check what's availableKotlin
fun canUseStrongBiometrics(context: Context): Boolean =
    BiometricManager.from(context)
        .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS
// Other results: BIOMETRIC_ERROR_NONE_ENROLLED (offer enrollment), NO_HARDWARE, HW_UNAVAILABLE…
AuthenticatorMeaningUse with crypto?
BIOMETRIC_STRONGClass 3 biometrics (meet strong security requirements)Yes
BIOMETRIC_WEAKClass 2, including some face unlock implementationsNo
DEVICE_CREDENTIALPIN, pattern, passwordYes, on newer Android versions

Crypto-bound authentication (recommended)

BiometricVault.ktKotlin
class BiometricVault(private val activity: FragmentActivity) {
    private val alias = "vault_key"

    private fun key(): SecretKey {
        val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
        (ks.getKey(alias, null) as? SecretKey)?.let { return it }
        val spec = KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)   // every use
            .setInvalidatedByBiometricEnrollment(true)
            .build()
        return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore").apply { init(spec) }.generateKey()
    }

    /** Decrypts [data] (IV + ciphertext) only after a successful strong biometric. */
    fun decrypt(data: ByteArray, onResult: (ByteArray?) -> Unit) {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
            init(Cipher.DECRYPT_MODE, key(), GCMParameterSpec(128, data.copyOfRange(0, 12)))
        }
        val prompt = BiometricPrompt(activity, ContextCompat.getMainExecutor(activity),
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    val unlocked = result.cryptoObject?.cipher ?: return onResult(null)
                    onResult(unlocked.doFinal(data, 12, data.size - 12))
                }
                override fun onAuthenticationError(code: Int, msg: CharSequence) = onResult(null)
            })
        prompt.authenticate(
            BiometricPrompt.PromptInfo.Builder()
                .setTitle("Unlock your vault")
                .setSubtitle("Confirm it's you")
                .setNegativeButtonText("Cancel")
                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
                .build(),
            BiometricPrompt.CryptoObject(cipher),
        )
    }
}

Encrypting works the same way: initialise the cipher in ENCRYPT_MODE and store cipher.iv + ciphertext. Handle KeyPermanentlyInvalidatedException when init runs: delete the key and ask the user to sign in again.

Server-verified biometric confirmation

For high-value actions such as payment confirmation, generate an auth-bound signing key pair, register the public key with your server, then sign a server-issued challenge after BiometricPrompt. The server verifies the signature, which proves both the device and the user's presence.

UX guidelines

  • Offer biometrics as an opt-in convenience after a normal sign-in, and explain what it protects.
  • Provide a fallback (device credential or password) for users without biometrics, or when it fails.
  • Don't prompt repeatedly. Use reasonable session timeouts for app-lock scenarios.
  • Never claim biometric data is stored by your app. It isn't, and saying so confuses users.

Use cases

App lock

Crypto-bound unlock of locally encrypted data when opening a notes or finance app.

Confirm a transfer

Biometric-signed challenge verified by the server.

Quick sign-in

Decrypt a stored refresh token after biometric authentication. See Secure Token Storage.

Frequently asked questions

Can my app access fingerprint images?

No. Biometric data stays within secure hardware and the system. Apps receive only an authentication result.

Does face unlock count as strong?

It depends on the device implementation's class. Query BIOMETRIC_STRONG to know whether a strong method is available.

Should biometrics replace passwords?

Use biometrics to unlock locally stored credentials or confirm actions. Account recovery still needs another factor.

Conclusion

Use BiometricPrompt with BIOMETRIC_STRONG and a Keystore-bound CryptoObject, so authentication actually unlocks data or signs server challenges. Provide fallbacks, handle key invalidation, and keep biometrics as a convenient layer on top of solid account security.

Next step: protect user data in AI features with Protecting User Data in AI-Powered Android Apps.

Official references

πŸ“± Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps β†’

Ready to build real Android applications?

Security is easiest to get right when it is part of how you build from the first screen. Strengthen the Android fundamentals that secure, privacy-respecting apps are built on.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp