How to Use Android Fingerprint Security

1

PIN authentication fades into the background because fingerprint authentication is more quickly and comfortable for application users. According to Androidauthority, over 70 percent of smartphones shipped in 2018 will have fingerprint sensors

You can see global smartphone fingerprint sensor penetration on the graphic below:

3

Credited from ‘androidauthority’ webpage

Android provides different ways of using fingerprint authenticate. The most popular of them is FingerprintManager added in Android Marshmallow (API level 23). But with the release of the Android Pie (API level 28) it was deprecated. FingerprintManager was replaced by BiometricPrompt. It’s a class that manages a system-provided biometric dialogue. For support device with versions less than API 28 android support library has BiometricPromtCompat, but sometimes it works incorrect (with bugs and application crash). For temporarily fix we can create your own realization and provide FingerprintManager (for 23 =< API version < 28) and BiometricPrompt (API version >= 28) with our own custom AuthManager.

So, let’s start to provide both of them into your project.

What provides Fingerprint AuthManager? 

It’ll be a simple manager with a provider which will allow using BiometricPrompt for devices with API version >= 28 and FingerprintManager for devices with API version < 28 (but not less than 23).

But previously let’s create and talk about fingerprint device states.

  • VERSION_DOES_NOT_ALLOW state: this device state assigned for all devices which have API version less than 23;
  • DEVICE_HARDWARE_DOES_NOT_ALLOW state: this device state assigned for devices which support API version for fingerprint but doesn’t have fingerprint feature;
  • NO_ENROLLED_FINGERPRINTS state: this device state assigned for devices than device has fingerprint feature but the system hasn’t enrolled fingerprints;
  • FINGERPRINT_ALLOW state: this state assigned for devices which support fingerprint feature and has enrolled fingerprints.
  • UNKNOWN_STATE: this state assigned when android can’t provide fingerprint system service.

So, according to states, we should create an enum class with context extension function for check Android fingerprint device state.

Enum class

enum class FingerprintState(val stateMessage: String) {
 FINGERPRINT_ALLOW("Everything will be nice :)"),
 VERSION_DOES_NOT_ALLOW("Your device version doesn't allow for fingerprint"),
 DEVICE_HARDWARE_DOES_NOT_ALLOW("Your device hasn't hardware for fingerprint"),
 NO_ENROLLED_FINGERPRINTS("Please, add fingerprint for device in security settings"),
 UNKNOWN_STATE("Ooooops, wtf");
}

Context extension method

fun Context.checkDeviceFingerprintState() =
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 val fingerprintManager = this.getSystemService(Context.FINGERPRINT_SERVICE) as? FingerprintManager

 if (fingerprintManager == null) {
 FingerprintState.UNKNOWN_STATE
 } else if (!fingerprintManager.isHardwareDetected) {
 FingerprintState.DEVICE_HARDWARE_DOES_NOT_ALLOW
 } else if (!fingerprintManager.hasEnrolledFingerprints()) {
 FingerprintState.NO_ENROLLED_FINGERPRINTS
 } else {
 FingerprintState.FINGERPRINT_ALLOW
 }
 } else {
 FingerprintState.VERSION_DOES_NOT_ALLOW
 }

Next step is creating AuthManager. It’ll be base cascade for biometric and fingerprint managers (look at the pseudo scheme below).

2

AuthManager it’s a simple Kotlin interface and Biometric, Fingerprint managers are realization according to API version.

For listen, user auth state AuthenticationListener should be created. There are three states when a user using a fingerprint feature:

  • Authenticate was successful;
  • Authenticate was failed;
  • Authenticate was cancelled.

AuthManager interface

interface AuthManager {
 fun authenticate()
 fun attachAuthListener(authenticationListener: AuthenticationListener)
}

Authentication listener interface

interface AuthenticationListener {
 fun onAuthSuccess()
 fun onAuthFailed()
 fun onAuthCancel()
}

Now let’s create realizations that implement AuthManager interface, but previously we must add permissions in the Android manifest file:

  <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
 <uses-permission android:name="android.permission.USE_FINGERPRINT"/>

Biometric AuthManager

For creating Biometric manager, we should use BiometricPrompt. Builder which builds BiometricPrompt instance. As you remember, BiometricPrompt is a simple dialogue for authenticating with user fingerprint. So, you can build a biometric dialogue with your own title, subtitle, description, etc.

For call system touch sensor listen you should call authenticate() method from BiometricPrompt instance. You must set AuthenticationCallback and CancellationSignal. It’ll look like in the code below:

override fun authenticate() {
 val appName = context.getString(R.string.app_name)
 val biometricPrompt = BiometricPrompt.Builder(context)
 .setTitle(context.getString(R.string.fingerprint_dialog_title_text))
 .setDescription(context.getString(R.string.biometric_dialog_description_text, appName))
 .setNegativeButton(
 context.getString(R.string.cancel_button_text), context.mainExecutor,
 DialogInterface.OnClickListener { _, _ -> mAuthenticationListener?.onAuthCancel() })
 .build()

 biometricPrompt.authenticate(
 getCancellationSignal(),
 context.mainExecutor,
 mBiometricAuthCallback
 )
 }

 private fun getCancellationSignal(): CancellationSignal {
 // With this cancel signal, we can cancel biometric prompt operation
 val cancellationSignal = CancellationSignal()
 cancellationSignal.setOnCancelListener {
 mAuthenticationListener?.onAuthCancel()
 }
 return cancellationSignal
 }

You can also set CryproObject for BiometricPrompt authenticate() method.

Using AuthenticationCallback for BiometricPrompt authenticate() method we can send events with our AuthenticationListener like in the code below:

private val mBiometricAuthCallback = object : BiometricPrompt.AuthenticationCallback() {

 override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
 mAuthenticationListener?.onAuthFailed()
 }

 override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
 mAuthenticationListener?.onAuthSuccess()
 }

 override fun onAuthenticationFailed() {
 mAuthenticationListener?.onAuthFailed()
 }
 }

Fingerprint AuthManager

For realization this manager we can create your own dialogue. We should start and stop listening for touch sensor (authenticate() method in FingerprintManager), and initialize FingerprintManager.CryptoObject for this (like in the code below).

private fun isCipherInitSuccess(): Boolean {
 return try {
 initKeyStoreAndKeyGenerator()
 mKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
 mKeyStore.load(null)
 val cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION)
 cipher.init(Cipher.ENCRYPT_MODE, mKeyStore.getKey(BIP_KEY_ALIAS, null))
 mCryptoObject = FingerprintManager.CryptoObject(cipher)
 true
 } catch (e: Exception) {
 Log.d(TAG, "isCipherInitSuccess: ${e.message}")
 false
 }
 }
private fun initKeyStoreAndKeyGenerator() {
 fun logException(e: Exception) {
 Log.d(TAG, "initKeyStoreAndKeyGenerator: ${e.message}")
 e.printStackTrace()
 }

 try {
 mKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
 mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)

 mKeyStore.load(null)

 val keyProperties = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT

 val builder = KeyGenParameterSpec.Builder(BIP_KEY_ALIAS, keyProperties)
 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
 .setUserAuthenticationRequired(true)
 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
 mKeyGenerator.init(builder.build())
 mKeyGenerator.generateKey()

 } catch (e: KeyStoreException) {
 logException(e)
 } catch (e: IOException) {
 logException(e)
 } catch (e: NoSuchAlgorithmException) {
 logException(e)
 } catch (e: CertificateException) {
 logException(e)
 } catch (e: InvalidAlgorithmParameterException) {
 logException(e)
 }
 }

For creating separation between our own fingerprint dialogue logic and UI logic we can create Fingerprint UiHelper with Lottie animation realization. Also, we should listen to FingerprintManager.AuthenticationCallback() for change Lottie animation and send our events using our AuthenticationCallback.

P.S. Creating fingerprint authenticate dialogue is very useful for creating and customizing dialogue design that should match the design of your application.

AuthManager provider

For providing auth manager you are can use DI libraries. Or you can create a provider which throw our own exception with FingerprintState instance like this:

@RequiresApi(api = Build.VERSION_CODES.M)
class AuthManagerProvider {
 companion object {
 @Throws(AuthManagerProvideException::class)
 fun provideAuthManager(context: Context, authenticationListener: AuthenticationListener): AuthManager {
 val fingerprintState = context.checkDeviceFingerprintState()
 return if (fingerprintState == FingerprintState.FINGERPRINT_ALLOW) {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && context.isBiometricSupported()) {
 BiometricAuthManager(context).apply {
 attachAuthListener(authenticationListener)
 }
 } else {
 FingerprintAuthManager(context).apply {
 attachAuthListener(authenticationListener)
 }
 }
 } else {
 throw AuthManagerProvideException(fingerprintState)
 }
 }
 }
}


After AuthManager providing, you can use authenticate() method for authenticating user, listen AuthenticationListener and create your own realization for listener methods.

You can check the full code in  GitHub.

Please, estimate my article. I did my best!
1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 5.00 out of 5)
Loading…
Share
Share