Overview
This blog is about implementing advance authentication using fingerprint in android app as Android Marshmallow and above version has introduced a set of API that makes easy to use touch sensor to develop an Android fingerprint app.
Why to use fingerprint authentication?
There are several benefits you will get by putting fingerprint authentication in your application
- Quick and reliable way of authenticating user’s identity
- Secure: Using Fingerprint authentication, online transactions become more convenient as Unique fingerprints assure that it’s unlocked just by you and impossible to guess.
Few steps to follow to enable fingerprint authentication in your app.
- Verify that the device is running on Android 6.0 (M)(minSdkversion-23) or above
- Verify that the device features a fingerprint sensor.
- Verify that the lock screen is protected by PIN, password or pattern and at least one fingerprint is registered on the smartphone
- Get access to Android keystore to store the key used to encrypt/decrypt an object
- Generate an encryption key and the Cipher
- Start the authentication process
- Implement a callback class to handle authentication events
Updating Manifest
- First of all, We need to add USE_FINGERPRINT permission in your AndroidManifest.xml file
<uses-permission android: name="android.permission.USE_FINGERPRINT" />
- App is going to require access to the device’s touch sensor in order to receive fingertip touch events:
- By adding the following code to your application, you can make fingerprint authentication as necessary in your app then declare that your app requires a touch sensor.
<uses-feature android :name="android.hardware.fingerprint" android: required=“true"/>
- Above code will let users install app on specific devices that fulfil this hardware requirement and prevent your app from being installed on devices that don’t include this piece of hardware.
- However , it’s good practice to make touch sensor as preferred, but not required so that Google Play will then permit users to download your app even if their device doesn’t have a fingerprint sensor.
<uses-feature android: name="android.hardware.fingerprint" android: required=“false”/>
- If you do opt for this approach, then your app will need to check for the presence of a touch sensor at runtime and then disable its fingerprint authentication features, where appropriate.
User Interface
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_fingerprint" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" tools:context=“com.yudiz.FingerprintActivity”> <LinearLayout android:layout_width="match_parent" android:id="@+id/headerLayout" android:orientation="vertical" android:gravity="center" android:layout_marginTop="100dp" android:layout_height="wrap_content"> <ImageView android:layout_width="70dp" android:layout_height="70dp" android:src="@drawable/ic_action_fingerprint" android:id="@+id/icon" android:paddingTop="2dp" android:layout_marginBottom="30dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/textPrimary" android:textSize="16sp" android:textAlignment="center" android:gravity="center" android:id="@+id/desc" android:text="please place your finger to verify your identity" android:layout_margin="16dp" android:paddingEnd="30dp" android:paddingStart="30dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/textPrimary" android:textSize="16sp" android:textAlignment="center" android:gravity="center" android:paddingEnd="30dp" android:id="@+id/desc" android:layout_margin="16dp" android:paddingStart="30dp"/> </LinearLayout> </RelativeLayout>
It’s time to create fingerprint authentication part
Part 1: Check whether the device has the hardware, software and settings required to support fingerprint authentication
- Verify Secure lock screen using keyguardManager and FingerPrintManager
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); FingerprintManager fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
- Verify hardware requirement, Runtime permissions and software settings
If all the conditions are met then app is ready to start authentication process
Part 2: Create the key, cipher and CryptoObject that we’ll use to perform the actual authentication.
- First of all, by generating keystore instance, gain access to keystore instance which allows you to store cryptographic keys which makes difficult to access device.
- Generate app’s unique encryption key
- Obtain a reference to the Keystore using the standard Android keystore container identifier (“AndroidKeystore”)
- Generate key
- Initialize an empty keystore
- Initialize a keyGenerator
- Configure this key so that the user has to confirm their identity with a fingerprint each time they want to use it
keyStore = KeyStore.getInstance("AndroidKeyStore"); keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,"AndroidKeyStore"); keyStore.load(null); keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) .setEncryptionPaddings( KeyProperties.ENCRYPTION_PADDING_PKCS7).build()); keyGenerator.generateKey();
Initialise cipher that will be used to create the encrypted FingerprintManager.
Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); keyStore.load(null); SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null); cipher.init(Cipher.ENCRYPT_MODE, key);
Assign the CryptoObject by creating cipher instance to the instantiated and various other checks before initiating the authentication process.
cryptoObject = new FingerprintManager.CryptoObject(cipher); Helper helper = new FingerprintHandler(this); helper.startAuth(fingerprintManager, cryptoObject);
Part 3: Create Helper Class
- Create helper class which extends FingerprintManager.AuthenticationCallback which will override 4 methods:
- onAuthenticationFailed() willl be called whenever fingerprint doesn’t match with anyone’s fingerprint registered on device.
- onAuthenticationError(int errMsgId, CharSequence errString) will be called when fatal error has occurred.
- onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) will be called when fingerprint has been successfully matched.
- onAuthenticationHelp(int helpMsgId, CharSequence helpString).
This is one of the important methods which will be called when a non Fatal error has occurred which provides additional information about error.
- Initialize CancellationSignal method whenever your app can no longer process user input. If you don’t use this method, then other apps will be unable to access the touch sensor, including the lockscreen!!!!!
MainActivity.java
public class MainActivity extends AppCompatActivity { private KeyStore keyStore; private static final String KEY_NAME = "fprint"; private Cipher cipher; private CryptoObject cryptoObject; private KeyGenerator keygenerator; private KeyguardManager keyguardManager; private FingerprintManager fingerprintManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding= DataBindingUtil.setContentView(this,R.layout.activity_main); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE); if (!fingerprintManager.isHardwareDetected()) { mBinding.desc.setText("Your device doesn't support fingerprint authentication"); } /* Runtime permission */ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) !=PackageManager.PERMISSION_GRANTED) { mBinding.desc.setText("Please enable the fingerprint permission"); } if (!fingerprintManager.hasEnrolledFingerprints()) { mBinding.desc.setText("No fingerprint found. Please register minimum one fingerprint"); } if (!keyguardManager.isKeyguardSecure()) { mBinding.desc.setText("Please enable lockscreen password in your device's Settings"); } else { try { generateEncryptionKey(); } catch (FingerprintException e) { e.printStackTrace(); } if (initializeCipher()) { cryptoObject = new FingerprintManager.CryptoObject(cipher); Helper helper = new Helper(this); helper.startAuth(fingerprintManager, cryptoObject); } } } } private void generateEncryptionKey() throws FingerprintAuthException { try { keyStore = KeyStore.getInstance("AndroidKeyStore"); keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); keyStore.load(null); keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .build()); keyGenerator.generateKey(); } catch (KeyStoreException | NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException | CertificateException | IOException exc) { exc.printStackTrace(); throw new FingerprintAuthException(exc); } } public boolean initializeCipher() { try { cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException("Failed to get Cipher", e); } try { keyStore.load(null); SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null); cipher.init(Cipher.ENCRYPT_MODE, key); return true; } catch (KeyPermanentlyInvalidatedException e) { return false; } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("Failed to init Cipher", e); } } private class FingerprintAuthException extends Exception { public FingerprintAuthException(Exception e) { super(e); } }
Helper.java
public class Helper extends FingerprintManager.AuthenticationCallback { private Context context; public Helper(Context mContext) { context = mContext; } public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { CancellationSignal cancellationSignal = new CancellationSignal(); if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { return; } manager.authenticate(cryptoObject, cancellationSignal, 0, this, null); } @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { this.update("Fingerprint Authentication error\n" + errString); } @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { this.update("Fingerprint Authentication help\n" + helpString); } @Override public void onAuthenticationFailed() { this.update("Fingerprint Authentication failed."); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { ((Activity) context).finish(); TextView textView = (TextView) ((Activity)context).findViewById(R.id.desc); textView.setText("Fingerprint authentication succeded"); } private void update(String e){ TextView textView = (TextView) ((Activity)context).findViewById(R.id.desc); textView.setText(e); } }
How To Test your App In Android Emulator
- To test the app, it is possible to use a real device that has a touch sensor. Anyway, it is possible to test the app in the emulator too.
- To use this app in Android Emulator, you have to first configure the fingerprint accessing to the Security menu. When the system asks for fingerprint you have to use the adb command to emulate the finger touch:
- Open your Mac’s Terminal (or Command Prompt if you’re a Windows user) then change directory (cd) so it’s pointing at your Android SDK download; specifically, the Android/sdk/platform-tools folder and fire this command.
adb -e emu finger touch <finger_id>
- On Windows, you may have to run telnet 127.0.0.1 followed by finger touch .
The image below shows the app in action: