You integrated biometrics into an Android app, but it doesn't work on Xiaomi? Or using the deprecated FingerprintManager that will crash on Android 14? We help implement reliable fingerprint authentication with BiometricPrompt and CryptoObject. Turnkey solution with compatibility guarantee across all brands. We have 5+ years of mobile development experience and have implemented biometrics in 30+ projects. Integration cost is determined after analysis—contact us for a quote.
Why BiometricPrompt is better than FingerprintManager?
FingerprintManager (deprecated since API 28) supports only fingerprint, doesn't work with CryptoObject, and is unstable on custom ROMs. BiometricPrompt from the androidx.biometric library (works from API 23+) supports fingerprint, face, iris—and most importantly, allows binding authentication to a cryptographic key via CryptoObject. This raises security to Class 3 (Strong)—the highest biometric category in Android.
| Parameter | FingerprintManager | BiometricPrompt |
|---|---|---|
| Minimum API | 23 (fingerprint only) | 23 (androidx), 28 (native) |
| Modality support | Fingerprint | Fingerprint, face, iris |
| CryptoObject | No | Yes |
| Spoof resistance | Weak (Class 2) | Strong (Class 3) |
| MIUI compatibility | Problematic | Stable with proper fallback |
Common mistakes
Mistake 1: Calling from ViewModel
BiometricPrompt requires a FragmentActivity or Fragment. Developers sometimes try to call it from ViewModel or Repository and get an IllegalStateException at runtime. The prompt lives in the UI layer, period. If you need to initiate authentication from business logic, use callbacks or an event-based approach.
Mistake 2: Missing CryptoObject
Many implementations call BiometricPrompt.authenticate() without CryptoObject, meaning they only check biometric presence but don't bind it to a cryptographic operation. This is "weak" biometrics: an attacker with root access could theoretically inject SUCCESS into the AuthenticationCallback. The correct approach is Class 3 (Strong) biometrics with CryptoObject. As per Android documentation, only CryptoObject provides cryptographic binding of authentication to the encryption operation.
Mistake 3: Android fragmentation
On MIUI 12–13, BiometricManager.canAuthenticate(BIOMETRIC_STRONG) returns BIOMETRIC_ERROR_NONE_ENROLLED even with registered fingerprints due to Xiaomi's customization. You need to add a fallback check using FingerprintManagerCompat for such cases. We guarantee correct operation on Samsung, Xiaomi, Pixel, and others—tested on 20+ models.
Correct implementation with CryptoObject
Key generation in Android Keystore
The essence: generate a key in Android Keystore bound to biometrics. Upon authentication, a Cipher is initialized with this key and passed to CryptoObject. If biometrics succeed, the cipher is unlocked and can encrypt/decrypt data.
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") keyGenerator.init( KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setInvalidatedByBiometricEnrollment(true) .build() ) keyGenerator.generateKey() setInvalidatedByBiometricEnrollment(true)—the key is invalidated when a new fingerprint is enrolled. Without this flag, the old key remains valid after the user changes biometrics, reducing security.
Initializing Cipher and CryptoObject
val cipher = Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}") val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val secretKey = keyStore.getKey(KEY_NAME, null) as SecretKey cipher.init(Cipher.ENCRYPT_MODE, secretKey) val cryptoObject = BiometricPrompt.CryptoObject(cipher) Then pass cryptoObject to biometricPrompt.authenticate(promptInfo, cryptoObject).
Handle callback completely
object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { val cipher = result.cryptoObject?.cipher ?: return // decrypt token from EncryptedSharedPreferences } override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { when (errorCode) { BiometricPrompt.ERROR_LOCKOUT -> showFallback() BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> showPermanentLockout() BiometricPrompt.ERROR_NEGATIVE_BUTTON -> showPinAuth() BiometricPrompt.ERROR_USER_CANCELED -> { /* do nothing */ } } } override fun onAuthenticationFailed() { // attempt failed but limit not exhausted—BiometricPrompt itself shows error } } onAuthenticationFailed is not a final error. The system updates the prompt UI itself. Do not hide the prompt or show your own errors in this callback.
Token storage
Use EncryptedSharedPreferences from androidx.security:security-crypto. Encrypt the token using the cipher from a successful CryptoObject, store encrypted bytes + IV in EncryptedSharedPreferences. On subsequent authentication: initialize biometrics in DECRYPT_MODE with the saved IV → get plaintext token.
Work process
| Step | Duration |
|---|---|
| Analysis and design | 1 day |
| Implement Keystore key and CryptoObject flow | 1–2 days |
| Prompt UI with custom text | 0.5 day |
| Handle all error codes | 0.5 day |
| Testing on real devices (Samsung, Xiaomi, Pixel) | 1–2 days |
| Unit test coverage | 1 day |
What’s included
- Integration documentation
- Source code with comments
- Build and signing instructions
- Support for 30 days after delivery
- Compatibility guarantee with Android 6.0+ and brands (Xiaomi, Samsung, Huawei)
We will assess your project within 1 day. Contact us for a consultation—we'll help you choose the right approach. Get a free timeline and cost estimate.
Step-by-step integration guide
- Add dependency
androidx.biometric:biometric:1.2.0-alpha05. - Create a key in Android Keystore with the parameters from the example above.
- Initialize Cipher and CryptoObject.
- Configure BiometricPrompt.PromptInfo with title and subtitle.
- Call
biometricPrompt.authenticate(promptInfo, cryptoObject). - Handle the callback—decrypt the token on
onAuthenticationSucceeded. - For storage, use EncryptedSharedPreferences.
Additionally, if you want to save on development, consider our standard implementation—it covers 90% of scenarios and reduces integration costs. Order biometric implementation right now—we'll prepare a commercial proposal within a day.







