linkSome More Biometric Messes

This is a follow-up to The Mess that is Android Biometrics, as we found even more wonderful surprises.

So the androidx biometric compat lib is out and stable. And it (mostly) blacklists Samsung devices so they are forced to use fingerprint. We can plop it in and all our woes are gone right? Sigh, I guess you can see where this is going. I wouldn't be writing another blog post otherwise.

When the user enables biometrics in our app, we show the biometrics prompt. While is not strictly necessary, it is a nice assurance to the user that it's working correctly. Well it turns out that while the blacklist solved the Samsung issue on login, it did not solve it for this prompt. This lead to an interesting concept about android's biometrics implementation that's not really called out anywhere in the documentation.

linkWait, not all biometrics are supposed to be secure?

Android actually supports two forms of biometrics: secure and insecure. Secure biometrics pass a certain level of security requirements which allows them to unlock encryption keys. Insecure biometrics... do not. What's the point in prompting for insecure biometrics that can't be used for encryption? Who knows? I guess security theater could be important to somebody. Well when we were showing the prompt to enable biometrics we were not passing in a cipher as we didn't need one. Unfortunately, this means that insecure biometrics are allowed and so it doesn't use the blacklist. So lesson learned: always pass in the cipher when showing the prompt to get a consistent experience.

linkDid anyone know this?

It seems like even the people who created the biometircs api didn't realize this. We got BiometricManager.canAuthenticate() in api 29, but this method does not distinguish between secure and insecure biometrics. So if you are using biometrics for it's intended purpose, it's useless. Luckily, there is another way. If you try to set up a key when the user has no secure biometrics enrolled it'll throw an exception.

1linkfun canSecurelyAuthenticate(): Boolean {

2link try {

3link val keystore = KeyStore.getInstance("AndroidKeyStore")

4link KeyGenerator.getInstance("AES", keystore.provider)

5link .init(

6link KeyGenParameterSpec.Builder("DUMMY_KEY_ALIAS", KeyProperties.PURPOSE_DECRYPT)

7link .setUserAuthenticationRequired(true)

8link .build()

9link )

10link return true

11link } catch (e: InvalidAlgorithmParameterException) {

12link // expected error if user isn't enrolled in secure biometrics

13link return false

14link } catch (e: Exception) {

15link // Log unexpected errors, though if there's an issue with the keystore we probably can't use

16link // biometrics anyway.

17link Log.w("keystore error", e)

18link return false

19link }

20link}

linkWhat now?

A feature request has been filed for having a secure form of BiometricMnager.canAuthenticate(). It appears that the necessary api will be available in Android 11 and hopefully there will be some androidx workaround. In the meantime, I've been maintaining a sample repo that collects all the workarounds necessary for getting biometrics to work correctly.

Some More Biometric MessesWait, not all biometrics are supposed to be secure?Did anyone know this?What now?

Home You Dont Need to Null Out Views Some More Biometric Messes The Mess that is Android Biometrics Random musing on Jetpack Compose