Authentication Using Fingerprint In Android - Tutorial

Gone are the days when you have to manually enter the username and password for login into some Android application. Not only you have to enter it manually, but it is also a time-consuming process. Also, if you forgot the password or username then you have to recover it by going through a series of steps. But on the other end, if we are using Fingerprint for Authentication, then there is no need to remember password. Also, no two persons can have the same Fingerprint, so, we need not worry about authenticity.

So, in this blog, we will learn how to use Fingerprint authentication in our Android applications. So, let’s get started.

Fingerprint Authentication overview

With the release of Android 6.0 (Android M), there has been a significant amount of changes to the APIs, one of them is Fingerprint Authentication. Now, we can easily implement Fingerprint Authentication in our application in the devices having the Fingerprint sensor. The whole process of Fingerprint Authentication can be summarized into the below steps:

  1. Requesting Fingerprint Authentication permission within the project’s manifest file.
  2. As fingerprints can only be registered on the devices which have its lock screen protected by a PIN, pattern or password. So, we have to check if the lock screen of the device is protected by a PIN, pattern or password.
  3. Then, create an instance of the FingerprintManager class.
  4. You have to gain access to the storage area that is used to store the cryptographic keys on Android devices i.e. Keystore. So, create an instance of the Keystore to gain access of the Android Keystore container. After that, generate an encryption key with the help of keyGenerator class and store it in the Keystore container.
  5. With the help of the key generated and stored in the Keystore container, initialize the instance of the Cipher class and use this instance to create a CryptoObject and assign it to FringerprintManager instance that you have created earlier.
  6. Call the authenticate method of the FingerprintManger class and implement methods to handle the callbacks.

Fingerprint Authentication Implementation

So, we have seen the theory of Fingerprint Authentication. Now let’s move on to the implementation part of the same.

Create a new project in Android Studio and name it according to your choice. Also, set the minimum API to 23 i.e. Android 6.0.

After creating the project, please ensure that your device has some kind of authentication other than fingerprint because the Fingerprint Authentication will work in that case only.

Add the permission of fingerprint in your androidmanifest.xml file. So, add the USE_FINGERPRINT permission in your manifest file:

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

Before moving towards the coding part of the app, let’s write the code for the UI part. Here in the UI, we will be having one ImageView and one TextView. So, the code for the activity_main.xml file is:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <ImageView
            android:layout_width="160dp"
            android:layout_height="160dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginBottom="8dp"
            android:src="@drawable/ic_fingerprint"
            android:layout_marginEnd="8dp"
            android:id="@+id/fingerprint_iv"/>

    <TextView
            android:id="@+id/fingerprint_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@+id/fingerprint_iv"
            android:layout_marginEnd="8dp"
            android:text="Touch the Fingerpeint Sensor"
            android:textSize="24sp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

You can replace the image of the ImageView according to your choice.

Fingerprint Authentication makes use of KeyguardManager and the FingerprintManager. So, in the onCreate() function, you need to obtain these two services:

class MainActivity : AppCompatActivity() {

    private lateinit var fingerprintManager: FingerprintManager
    private lateinit var keyguardManager: KeyguardManager

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (checkLockScreen()) {
            //some other task
        }
    }
    private fun checkLockScreen(): Boolean {
        keyguardManager = getSystemService(Context.KEYGUARD_SERVICE)
                as KeyguardManager
        fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE)
                as FingerprintManager
        //some other task
    }
}

Our next task is to check if the Lock Screen is PIN or password protected. Also, if it is password protected then we have check if some fingerprint is already associated with the device or not. So, we will perform these checking in the checkLockScreen() method of MainActivity.kt file.

private fun checkLockScreen(): Boolean {
    keyguardManager = getSystemService(Context.KEYGUARD_SERVICE)
            as KeyguardManager
    fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE)
            as FingerprintManager
    if (keyguardManager.isKeyguardSecure == false) {

        Toast.makeText(this,
            "Lock screen security not enabled",
            Toast.LENGTH_LONG).show()
        return false
    }

    if (ActivityCompat.checkSelfPermission(this,
            Manifest.permission.USE_FINGERPRINT) !=
        PackageManager.PERMISSION_GRANTED) {
        Toast.makeText(this,
            "Permission not enabled (Fingerprint)",
            Toast.LENGTH_LONG).show()

        return false
    }

    if (fingerprintManager.hasEnrolledFingerprints() == false) {
        Toast.makeText(this,
            "No fingerprint registered, please register",
            Toast.LENGTH_LONG).show()
        return false
    }
    return true
}

Now, we have to generate an encryption key which will be stored in the Android Keystore System. So, we have to gain access of the Keystore and then generate the encryption key with the help of generateKey() method.

class MainActivity : AppCompatActivity() {

    ...

    private lateinit var keyStore: KeyStore
    private lateinit var keyGenerator: KeyGenerator
    private val KEY_NAME = "my_key"

    ...

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (checkLockScreen()) {
            generateKey()
            //some code
        }
    }
    private fun checkLockScreen(): Boolean {
        //some code
    }
    private fun generateKey() {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore")
        } catch (e: Exception) {
            e.printStackTrace()
        }

        try {
            keyGenerator = KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES,
                "AndroidKeyStore")
        } catch (e: NoSuchAlgorithmException) {
            throw RuntimeException(
                "Failed to get KeyGenerator instance", e)
        } catch (e: NoSuchProviderException) {
            throw RuntimeException("Failed to get KeyGenerator instance", e)
        }

        try {
            keyStore.load(null)
            keyGenerator.init(
                KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(
                        KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build())
            keyGenerator.generateKey()
        } catch (e: NoSuchAlgorithmException) {
            throw RuntimeException(e)
        } catch (e: InvalidAlgorithmParameterException) {
            throw RuntimeException(e)
        } catch (e: CertificateException) {
            throw RuntimeException(e)
        } catch (e: IOException) {
            throw RuntimeException(e)
        }
    }
}

So, till now, we have generated the key. Our next task is to initialize the cipher that will be used for CryptoObject instance. This CryptoObject will be used during the fingerprint authentication process. So, create a method called initCipher() in the MainActivity.kt file:

class MainActivity : AppCompatActivity() {

    ...
    
    private lateinit var cipher: Cipher
    private lateinit var cryptoObject: FingerprintManager.CryptoObject

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (checkLockScreen()) {
            generateKey()
            if (initCipher()) {
                cipher.let {
                    cryptoObject = FingerprintManager.CryptoObject(it)
                }
            }
        }
    }
    private fun checkLockScreen(): Boolean {
        //some code
    }
    private fun generateKey() {
        //some code
    }

    private fun initCipher(): Boolean {
        try {
            cipher = Cipher.getInstance(
                KeyProperties.KEY_ALGORITHM_AES + "/"
                        + KeyProperties.BLOCK_MODE_CBC + "/"
                        + KeyProperties.ENCRYPTION_PADDING_PKCS7)
        } catch (e: NoSuchAlgorithmException) {
            throw RuntimeException("Failed to get Cipher", e)
        } catch (e: NoSuchPaddingException) {
            throw RuntimeException("Failed to get Cipher", e)
        }

        try {
            keyStore.load(null)
            val key = keyStore.getKey(KEY_NAME, null) as SecretKey
            cipher.init(Cipher.ENCRYPT_MODE, key)
            return true
        } catch (e: KeyPermanentlyInvalidatedException) {
            return false
        } catch (e: KeyStoreException) {
            throw RuntimeException("Failed to init Cipher", e)
        } catch (e: CertificateException) {
            throw RuntimeException("Failed to init Cipher", e)
        } catch (e: UnrecoverableKeyException) {
            throw RuntimeException("Failed to init Cipher", e)
        } catch (e: IOException) {
            throw RuntimeException("Failed to init Cipher", e)
        } catch (e: NoSuchAlgorithmException) {
            throw RuntimeException("Failed to init Cipher", e)
        } catch (e: InvalidKeyException) {
            throw RuntimeException("Failed to init Cipher", e)
        }
    }
}

So, we are done with key generation, cipher and cipher object. Whenever we need Fingerprint Authentication, the authenticate method of the FingerprintManager is called and as a result of this, a number of events may occur based on the failure or success of the authentication. So, these callback events and the authenticate method must be implemented in a class that extends the FingerprintManager.AuthenticationCallback. In your project, add one class named FingerprintHelper.kt and add the below lines of code:

@SuppressLint("ByteOrderMark")
class FingerprintHelper(private val appContext: Context) : FingerprintManager.AuthenticationCallback() {

    lateinit var cancellationSignal: CancellationSignal

    fun startAuth(manager: FingerprintManager,
                   cryptoObject: FingerprintManager.CryptoObject) {

        cancellationSignal = CancellationSignal()

        if (ActivityCompat.checkSelfPermission(appContext,
                Manifest.permission.USE_FINGERPRINT) !=
            PackageManager.PERMISSION_GRANTED) {
            return
        }
        manager.authenticate(cryptoObject, cancellationSignal, 0, this, null)
    }

    override fun onAuthenticationError(errMsgId: Int,
                                        errString: CharSequence) {
        Toast.makeText(appContext,
            "Authentication error\n" + errString,
            Toast.LENGTH_LONG).show()
    }

    override fun onAuthenticationHelp(helpMsgId: Int,
                                      helpString: CharSequence) {
        Toast.makeText(appContext,
            "Authentication help\n" + helpString,
            Toast.LENGTH_LONG).show()
    }

    override fun onAuthenticationFailed() {
        Toast.makeText(appContext,
            "Authentication failed.",
            Toast.LENGTH_LONG).show()
    }

    override fun onAuthenticationSucceeded(
        result: FingerprintManager.AuthenticationResult) {

        Toast.makeText(appContext,
            "Authentication succeeded.",
            Toast.LENGTH_LONG).show()
    }
}

Now, finally in the onCreate method of the MainActivity we have to create a new instance of the FingerprintHelper class to start the startAuth method.

override fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    if (checkLockScreen()) {
        generateKey()
        if (initCipher()) {
            cipher.let {
                cryptoObject = FingerprintManager.CryptoObject(it)
            }
            val helper = FingerprintHelper(this)

            if (fingerprintManager != null && cryptoObject != null) {
                helper.startAuth(fingerprintManager, cryptoObject)
            }
        }
    }
}

That’s it.

Run the application on your device having the fingerprint sensor. Also, try different situations. For example, delete all fingerprints associated with your device, delete the password of your device.

Hope you liked this blog. To learn more about Android, you can visit our blogging website.

Keep Learning :)

Team Mindorks!