App Development

Android Fingerprint APIs: An Overview for Android App Developers

blog-featured-image android fingerprint APIs

Android Fingerprint APIs are bringing user authentication to a whole new level, making it fast and secure. Unlocking a phone with a single touch is one of my favorite features in Marshmallow and I really wish there were more apps out there using touch identification. Fingerprint recognition itself is not new, but the OS-level support for it in Android has been much anticipated. In the near future, it’s going to eliminate the need to integrate specific fingerprint SDKs from device manufacturers like Samsung, which, without a doubt, would be a great relief for app developers.

This overview will familiarize you with the workflow for using Android 6.0 Fingerprint APIs based on sample projects from Google. A few main points to keep in mind:

  • There are not many devices (yet) running Android 6.0+ that have the fingerprint hardware.
  • For certain devices (for example, Samsung Galaxy phones) that have the hardware but are not running Android 6.0+, you’ll have to use the SDK from the manufacturer.
  • Android Marshmallow has introduced a new permissions model that requires the user to give you sensitive permissions at runtime. Therefore, take into account that the user might not grant your app permission for fingerprint scanning.
  • You can create a symmetric key or asymmetric key pair for data encryption (see this post for more details and code example on the asymmetric key flow for Android apps).
  • Keep the UI user-friendly. Consult these design specifications from Google. Make sure that the UI indicates when the scanner is ready for the user. It is recommended to use Google’s standard fingerprint icon which is easily recognized by users.
  • Don’t forget to cancel listening to a fingerprint when your app goes to the background. If you don’t stop listening, other apps can’t use the scanner, including the lock screen. In other words, treat the fingerprint reader like a camera. (Tidbit from Eric Richardson )

Let’s get started!

  1. Set up the SDK and permissions in the manifest First, set your targetSdkVersion to “23” and add the USE_FINGERPRINT permission in your manifest.

  2. Provide an instance of KeyStore and KeyGenerator “AndroidKeyStore” is registered as a KeyStore type for use with the KeyStore.getInstance(type) method and KeyGenerator.getInstance(algorithm, provider) methods.

  3. Request a permission at runtime Call requestPermissions() in your Activity’s onCreate():

    requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, FINGERPRINT_PERMISSION_REQUEST_CODE);

The FINGERPRINT_PERMISSION_REQUEST_CODE is a request code value to match with a result reported to onRequestPermissionsResult(int, String[], int[]). You can choose any value you want.

  1. Check if the permission was granted Now, in this callback you will check if the permission was granted:

@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) { }}

Initialize your UI in the body of onRequestPermissionsResult().

  1. Make sure the hardware is present and functional FingerprintManager is a class that coordinates the scanner hardware.

    @Provides public FingerprintManager providesFingerprintManager(Context context) { return context.getSystemService(FingerprintManager.class); }

Checking if the scanner exists is easy with FingerprintManager.isHardwareDetected().

  1. Check that the lock screen has been set up To check if the user has set up their lock screen, get an instance of KeyguardManager by calling getSystemService(java.lang.String) with the argument KEYGUARD_SERVICE, and use the method KeyguardManager.isKeyguardSecure(). To check if there are any fingerprints enrolled, use FingerprintManager.hasEnrolledFingerprints().

  2. Create a key / key pair Initialize an empty KeyStore by passing null in KeyStore.load(). A KeyGenerator or a KeyPairGenerator (for asymmetric key pair) is the class used to create keys. It must be initialized with with a KeyGenParameterSpec instance created by KeyGenParameterSpec.Builder. A key or key pair is created with generateKey() or generateKeyPair() methods. KeyGenParameterSpec specifies for which operation the key can be used (encryption, decryption, etc.), block modes, expiration date and other parameters. Note that if you are creating an asymmetric key pair, this spec would apply only to private keys. Public keys can be used for any supported operations. It’s important that you require the user to authenticate with a fingerprint to authorize every use of the key by adding setUserAuthenticationRequired(true) in KeyGenParameterSpec.Builder.

    public void createKey() { try { mKeyStore.load(null); // Set the alias of the entry in Android KeyStore where the key will appear // and the constrains (purposes) in the constructor of the Builder mKeyGenerator.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()); mKeyGenerator.generateKey(); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertificateException | IOException e) { throw new RuntimeException(e); } }

After the key has successfully been created, you can enable the UI for authentication. A public key should be transferred to your backend server at this point.

  1. Initialize a Cipher (or Signature for asymmetric key pair) When the user taps the authentication button, initialize a cipher for a certain operational mode (one of: encryption, decryption, key wrapping or key unwrapping).

    private boolean initCipher() { try { mKeyStore.load(null); SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); mCipher.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); } }

initCipher() could fail if the lock screen has been reset or disabled, or a fingerprint was enrolled after the key has been generated. Then you should authenticate with a password first and ask the user if they want to authenticate with fingerprints in the future.

In case of asymmetric key pair, we would work with a Signature.

@Provides
    public Signature providesSignature(KeyStore keyStore) {
        try {
            return Signature.getInstance("SHA256withECDSA");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to get an instance of Signature", e);
        }
    }

The Signature must be initialized with a certain algorithm.

private boolean initSignature() {
        try {
            mKeyStore.load(null);
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null);
            mSignature.initSign(key);
            return true;
        } catch (KeyPermanentlyInvalidatedException e) {
            return false;
        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
                | NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Failed to init", e);
        }
    }
  1. Listen for user’s fingerprint and set authentication callbacks After the cipher/signature initialization, we can move on to the actual fingerprint authentication. To authenticate with FingerprintManager, we’ll need:
  • A FingerprintManager.CryptoObject - a wrapper class for the crypto objects supported by FingerprintManager. You can pass a Signature, Cipher or Message Authentication Code (MAC) in its constructor.new FingerprintManager.CryptoObject(mCipher);
  • A new CancellationSignal instance, which provides the ability to cancel an operation that’s in progress.
  • The flags in FingerprintManager’s authentication method are optional and can be set to 0.
  • Authentication callbacks (passed as this in the example below). In Google’s sample, the FingerprintUiHelper class extends FingerprintManager.AuthenticationCallback and contains the authentication callbacks for the FingerprintManager. It shows the appropriate message to the user according to the result of fingerprint authentication. These are the callback methods that it implements:
    • void onAuthenticationError(int errorCode, CharSequence errString) Called when an unrecoverable error has been encountered and the operation is complete.
    • void onAuthenticationFailed() Called when a fingerprint is valid but not recognized.
    • void onAuthenticationHelp(int helpCode, CharSequence helpString) Called when a recoverable error has been encountered during authentication.
    • void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) Called when a fingerprint is recognized.

You might want to give the user more detailed feedback on why their fingerprint has not been recognized (for example, “Sensor is dirty”). The error and help callback methods provide a description string for this purpose. Consult this list of result codes from the official documentation.

  • A handler for callback events is also optional. If a Handler object is provided, the FingerprintManager will use the Looper from that object when processing the messages from the fingerprint hardware.

    public boolean isFingerprintAuthAvailable() { return mFingerprintManager.isHardwareDetected() && mFingerprintManager.hasEnrolledFingerprints(); }

public void startListening(FingerprintManager.CryptoObject cryptoObject) {
        if (!isFingerprintAuthAvailable()) {
            return;
        }
        mCancellationSignal = new CancellationSignal();
        mSelfCancelled = false;
        mFingerprintManager
                .authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
        mIcon.setImageResource(R.drawable.ic_fp_40px);
    }


    public void stopListening() {
        if (mCancellationSignal != null) {
            mSelfCancelled = true;
            mCancellationSignal.cancel();
            mCancellationSignal = null;
        }
    }

Make sure your app is not waiting for a fingerprint when it goes to background.

@Override
    public void onResume() {
        super.onResume();
        if (mStage == Stage.FINGERPRINT) {
            mFingerprintUiHelper.startListening(mCryptoObject);
        }
    }






    @Override
    public void onPause() {
        super.onPause();
        mFingerprintUiHelper.stopListening();
    }

If you have been using a Signature, you can sign your data after a successful fingerprint authentication:

signature.update(transaction.toByteArray());
byte[] sigBytes = signature.sign();

Then send it over to the backend so it can be verified with a public key.

If you are having any trouble with the steps described above, check out the FingerprintDialog and AsymmetricFingerprintDialog repositories.

A final note: At the moment, most apps would be targeting Android versions prior to 6.0. That would introduce a minor change in the code above. Basically, instead of using FingerprintManager class, you would be using FingerprintManagerCompat, which is included in Android Support Library v4, revision 23. On platforms before Marshmallow, this class behaves as if there is no fingerprint hardware available.

Join our team to work with Fortune 500 companies in solving real-world product strategy, design, and technical problems.

Find Your Role
Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article