Using BiometricPrompt with CryptoObject: how and why
Biometrics and Cryptography are not the same thing. They are in fact completely independent of each other:
- Cryptography is about hiding information from an adversary and validating the authenticity of that information. In cryptography, an adversary cannot read encrypted data without the secret key. Also, most cryptographic techniques have anti-tamper mechanisms.
- Biometrics, on the other hand, is for verifying personal identity using bodily measurements. In biometrics, a fingerprint, face, or other aspects of who you are can be used for authentication.
There are many systems on Android that work together to secure data. Since Android 4.4, the contents of the user’s data partition are encrypted by default. Generally Jetpack Security is a sufficiently developer-friendly option which does the heavy lifting of handling key generation in the AndroidKeyStore and provides abstractions for encrypting Files and SharedPreferences.
Even though cryptography doesn’t depend on biometrics, there are cases where it makes sense to use biometrics to protect your encryption key to provide an extra layer of security. These use cases include: enterprise, government, financial, and healthcare. This post explores several places where the Biometric Library’s CryptoObject
comes in handy for these use cases. Android’s biometric APIs support the following cryptographic operations — Cipher, MAC, and Signature. In this post, we’ll focus on Cipher.
To understand how the two systems come together, let’s first dive a bit into how cryptography works on Android. Then, we will show how biometrics can be used for an added layer of security to make your app more resilient against potential attackers.
Cryptography and key management on Android
At the core of the Java Crypto API is a Cipher
, an object that can be used to perform encryption and decryption of data. To apply a cipher, you need a SecretKey
object that references the underlying cryptographic secret key. Only someone with this secret key can use the cipher to decrypt your data. On Android, secret keys should be kept in a secure system called the Android Keystore. The purpose of the Android Keystore is to keep the key material outside of the Android operating system entirely, and in a secure location sometimes referred to as the Trusted Execution Environment (TEE) or the Strongbox. Wherever the key material exists, there’s a potential for an attacker to gain access to it. Therefore the Android Keystore keeps the key material as closely restricted as possible and ensures that the app, the Android userspace, and even the Linux kernel have no access to the material.
Say you wish to encrypt your app’s data on Android. When your app asks the Android KeyStore to create a secret key, the Keystore never actually gives your app the value of the SecretKey. That’s because the SecretKey is never allowed to leave the secure area. The actual process goes like this:
- Your app asks the Android
KeyStore
for aSecretKey
- The Android Keystore creates the secret key in the secure location (Strongbox or TEE).
- The Keystore returns an alias to your app. Only the Keystore knows how to map this alias to your newly-created
SecretKey
. - When your app wants to perform encryption, it asks the Keystore system to do it.
- The Keystore system takes in the plaintext and the alias, and it returns encrypted data, called ciphertext. (Note that
Cipher
is just one example. Other artifacts includeSignature
,Mac
, andIdentityCredential
.) - When your app wants to perform decryption, the Keystore system takes in the ciphertext and the alias and returns decrypted data, or plaintext.
Add biometrics to require user presence
Android by default already performs full disk encryption with PIN/pattern/password. You add biometric authentication in your app to ask the system to further guard your secret keys using authentication binding. Even if a device should become compromised and an attacker makes a request, the Android Keystore would still refuse to decrypt the data — unless the attacker can somehow get the user to authenticate with their biometric credentials. Biometric authentication adds an additional layer of security — even on a compromised device — because the hardware managed by Keystore cannot be accessed unless the user is present.
The beauty of using biometrics to lock your secret key is that, like all other cryptographic solutions on Android, all sensitive operations between the biometrics system and the Android Keystore system take place in the secure space (TEE/SE), far away from prying eyes. To understand why that’s important, let’s peek under the hood to see how biometrics works and then how the two systems interact.
Under the hood of the Android Biometric system
Android requires a strong chain of trust. The user must verify that they are who they say they are before they can add a biometric. This decision is made in TEE. At no point in this process is the biometric credential shared with your app or allowed to leave the secure space on the device.
In general, the Android Biometric system is used in two places, during enrollment (aka account setup) and during authentication (used in apps or keyguard).
When the user sets up a new account on their device, the following happens:
- They can choose to enroll their fingerprint (or face or iris).
- If the user decides to register a biometric credential, the associated sensor generates a template (also known as embedding) in the TEE.
Sometime later, your app allows the user to authenticate using biometrics. The framework, along with the Android Biometrics system in the TEE/SE, handles the actual authentication. This process involves the following steps:
- The user presents their biometric credential, such as a fingerprint, on the sensor.
- The system securely generates an embedding in the TEE and compares it to the embedding that was created during setup. That is, the biometrics system checks if the credential matches one that’s registered with the device.
- The framework relays the results to your app: either yes, the credential matches one of the registered templates, or no, the credential was not recognized.
- If a
CryptoObject
was used, an additional attestation is generated that can be later confirmed by the TEE that the biometric was authentic. This is used to unlock theCryptoObject
.
That, in a nutshell, is how the biometric system works on Android.
Working together: private data retrieval in a secure space
As you can see, both the Keystore system and the Biometric system on Android provide security measures on their own, especially since they keep their materials and sensitive operations in the secure space (TEE/SE). But data becomes even more secure when you require biometric authentication to unlock a SecretKey
that’s associated with your app. That’s because the whole transaction takes place in the secure space (TEE/SE). Still, it’s important to note that biometric authentication is only part of the process for encrypting data; it merely establishes that a user is present.
Here is how the Biometric and the Keystore systems work together to protect your users’ data:
- You, a developer, request that biometric authentication be required to access your secret key by setting
setUserAuthenticationRequired(true)
. - When your app requests data that’s associated with the
SecretKey
, the user gets a prompt to provide a valid biometric credential. - Recall that the biometric sensor communicates securely to the TEE so that neither the framework nor third-party apps are privy to the transaction. Hence, when the user taps the fingerprint sensor, the material is read directly by the TEE.
- If the biometric credential matches a credential that’s registered, the biometric component in the TEE mints a hardware authentication token (HAT). HATs contain an HMAC, which can be used to verify message integrity as well as authenticity.
- The biometric component in the TEE/SE and Keymaster, also within the TEE/SE, share a secret key. Thus, when the framework forwards this HAT to the Keystore system, Keymaster is able to validate the authenticity and integrity of the HAT and unlock the appropriate key.
- The biometric framework invokes your app’s
onAuthenticated()
callback.
Note that because the system is seeking to match biometric credentials with an existing template, by default the system will invalidate all existing keys associated with biometric authentication if a new biometric credential is enrolled. That way, an adversary cannot just register their fingerprint or face and use that to access your private data. The secret key will simply stop working and data dependent on it is essentially lost. Therefore, if your app handles high-value transactions, such as a banking app, monitor the callback that indicates when the user adds a new biometric credential. In that callback, inform the user and ask them if it was intentional before you allow the user to re-register their biometric credentials in your app.
The CryptoObject
Biometrics and Cryptography are so independent that in your own project the code that deals with biometric authentication and the code that deals with encryption/decryption don’t even need to be in the same class. The only place the two systems should ever cross paths in your project is your definition of CryptoObject
.
Furthermore, the CryptoObject
is just a wrapper that conveniently carries the cipher along to where you might need it. Your app passes in the CryptoObject
when it calls biometricPrompt.authenticate(CryptoObject)
and the BiometricPrompt
returns the exact same CryptoObject
to your app’s onAuthenticationSucceeded()
callback.
Admittedly, there is a little bit more to the story. The API also uses the CryptoObject
parameter as indication that you require a strong biometric sensor for authentication; therefore, if you don’t pass in the parameter, your app’s SecretKey
will not be unlocked after the user provides biometric authentication. Furthermore, a CryptoObject
is specific to a particular cryptographic operation, and the biometric authentication that the CryptoObject
is passed to unlocks only that one operation. This allows the developer to ensure that each use of the key must be authenticated/approved.
Now for some code
Here we will keep the cryptography portion simple. We will demonstrate how to use a Cipher
and a SecretKey
to symmetrically encrypt/decrypt data. You can learn more about cryptography on Android by reading through the pages on DAC.
To avoid confusion and for separation of concerns, you can put all your cryptography code in a single file and name the file CryptographyManager
. The file might look like this.
To further show that the separation of concerns is indeed real, the CryptographyManagerImpl
is declared private so that the client code only sees the interface and its method signatures.
After importing the biometrics Gradle dependency, build your PromptInfo
object and your BiometricPrompt
instance without concerning yourself with cryptography. In the code that presents an authentication dialog, such as MainActivity
, set up BiometricPrompt
as usual, as shown in the following steps.
1. Import the Gradle dependency
Always use the latest release of the library. As of the publication date for this post, the latest version is 1.0.1.
2. Create an instance of BiometricPrompt
3. Build a PromptInfo object
4. Business-specific OnClickListener
Now that you have your BiometricPrompt
and your PromptInfo
, what you do next depends on your app’s business logic. In our case, a user types into an EditText
object and then clicks either a Button
to encrypt the text or another Button
to decrypt any already-encrypted text.
Hence, for us, there are two click listeners: one calls the user to authenticate in order to encrypt data; another calls the user to authenticate in order to decrypt data. Because these onClick
methods must each pass in an instance of CryptoObject
when they call biometricPrompt.authenticate()
, the Cipher
is finally required.
Putting it all together
For us, the MainActivity
class looks as follows in the end:
Summary
In this post, you learned the following:
- The difference between the cryptography (Keystore) system and the biometric system on Android.
- How to protect your app-specific, secure data by associating it with a biometric.
- How to combine the two systems using
CryptoObject
to achieve a higher degree of security. - How to use
setUserAuthenticationRequired(true)
when defining yourSecretKey
in order to lock the key using authentication binding. - How to pass your cryptographic cipher to the
CryptoObject
when you authenticate in order to unlock the secret key.
The complete sample Android app demonstrating these concepts is at https://github.com/isaidamier/blogs.biometrics.cryptoBlog.
Happy Coding!