On Using Keys and Biometrics
Below are some findings on using a couple of security features in Android:
- Device locks
AndroidKeyStore is an implementation of the Java Cryptography Architecture (JCA)'s
KeyStore service to manage application-specific cryptographic keys. Such keys can be created or imported with an associated label, then an opaque
Key class obtained from that label to use for cryptographic operations. However, they cannot be exported. The remainder here focuses on creating rather than import.
Obtaining this Keystore is done using the static method
Keystore.getIstance() and specifying the "AndroidKeyStore" Service Provider Interface (SPI) provider.
New keys are created using an instance of
KeyPairGenerator), again specifying "AndroidKeyStore" as the SPI provider. When creating a new key, several properties can be applied via
- key size (in bits)
- encryption block modes (e.g.,
- encryption padding (e.g.,
- require authentication
- duration until next authentication (in seconds)
Once set, these properties cannot be changed without first deleting then re-importing/-creating the key. If the key creation requests some user authentication is required, it can only be done if the device has a security lock set (e.g., Pattern/PIN/Password); if user authentication is required for every use, it can only be done if the user has at least one fingerprint enrolled.
Keys in the AndroidKeyStore are stored on the device until one of the following happens:
- The app deletes its entry from the KeyStore
- The app's data storage (not data cache) is cleared
- The app is uninstalled
Keys with user authentication required are invalided and cannot be used if any of the following happen:
- The device is hard reset
- The security lock is disabled (e.g., changed from Pattern/PIN/Password to Swipe or None)
Further, "authenticate every use" keys are invalided and cannot be used if any of the following happen:
- A new fingerprint is enrolled
- All fingerprints are unenrolled
Existing Key objects (secret, private, public, and even certificates) are obtained using the Keystore instance's typical methods (e.g.,
.getSecretKey()). Note that all of the methods on the Key object that would export the value (e.g., getEncoded()) either throw exception or return
null; this is true even for PublicKeys.
the "Java Standard" Cipher/Mac/Signature classes are used in Android as they are in any other Java/Kotlin environment. If the key requires authentication, a
UserNotAuthenticatedException is thrown; if the key no longer valid (as above), a
KeyPermanentlyInvalidatedException is thrown.
The KeyguardManager is a system service that originally used to lock/unlock the keyboard, but has since expanded to lock/unlock the user's device. It can only be obtained from a
The most interesting methods here are those that determine if a "strong" security lock (Pattern/PIN/Password) is configured and if the device is currently locked. Both are boolean values; it is not possible to determine which method of lock is configured.
In addition, the KeyguardManager can be used to prompt the user to enter their Pattern/PIN/Password by way of
createConfirmDeviceCredentialIntent(); if no "strong" security lock is configured, this method returns
The Intent is created with optional title and description, then dispatched via
startActivityForResult() to trigger the device prompts. Applications receive either
RESULT_OK (if successfully unlocked) or
RESULT_CANCELED (device prompt is dismissed) via the overridden method
onActivityResult(). It is important to note that dispatching and monitoring is best done from a long-running Activity, such as the MainActivity, or the result is never received.
NOTE: This API is marked as deprecated as of API 28 (Android Pie) and replaced with
The FingerprintManager is a system service used to interact with a device's fingerprint hardware. This was added in API 23, and is now deprecated as of API 28. As with
KeyguardManager it can only be obtained from a
Activity). It also requires the
USE_BIOMETRIC (added in API 28) permission in the app's manifest.
There are methods to determine if fingerprint authentication is possible; detecting if hardware exists and there is at least one fingerprint enrolled.
Engaging the fingerprint reader is done by calling
authenticate(). Before doing so, the app must provide a
FingerprintManager.CryptoObject and a
FingerprintManager.AuthenticationCallback. This method returns immediately; further interaction happens via the passed-in
AuthenticationCallback. An optional
CancellationSignal can be provided to disengage the fingerprint hardware out-of-band (e.g., from the user clicking a "Cancel" button).
This object only engages the hardware; it does not display anything to the user itself. The app is responsible for managing a view regarding the fingerprint reading operations.
Once the reader has succeeded or errored, it is no longer valid; a new instance must be obtained.
About the CryptoObject
CryptoObject wraps a
Signature object, ready and initialized with the desired key. the scanner is engaged regardless of the key's authentication requirements, so even keys without any requirements can be used.
About the AuthenticationCallback
AuthenticationCallback is where events from fingerprint reader attempts are dispatched. Subclasses need only override the event methods they are interested in.
onAuthenticationSuccessful() is called with the original
CryptoObject wrapped in a
AuthenticationResult. If the key required authentication, it is now useable within this method's bounds (and only now if authentication is required on every use).
A fingerprint read failure is notified via
onAuthenticaitonFailed(), such as a unrecognized print. If there is some other temporary failure (e.g., dirty reader),
onAuthenticationHelp() is called with the relevant status code and a (device locale appropriate) user-directed help message.
Permanent errors are notified via
onAuthenticationError(), with the relevant status code and (device locale appropriate) user-directed error message.
The BiometricPrompt is a class used to engage a device's biometrics hardware using a system-provided dialog. This class is introduced in API 28 to replace FingerprintManager. The intent is to support not only fingerprint readers, but also facial recognition; it also handles hardware variations, such as in-screen fingerprint readers (e.g., display a user prompt that indicates the on-screen boundaries of the reader).
To create a
BiometricPrompt.Builder is created (with an appropriate
Context) and configured. The app can set a title, subtitle, description, and "cancel" button behavior. Once created, the app calls
authenticate() (just like with
The hardware-engagement behavior is nearly identical to
FingerprintManager; the app is notified of events via an instance of
BiometricPrompt.AuthenticationCallback (which has the exact same methods as
FingerprintManager.AuthenticationCallback), can be canceled via a
CancellationSignal, and operates on a
CryptoObject. The biggest differences are:
- The app must explicitly provide an
Executorwhere events are dispatched (this can be
- The app no longer manages any view to interact with the user.