Android Fingerprint Authentication

4 min read

There are a number of ways to authenticate a user to access an application, or a certain feature within an application, such as checkout, and fingerprint is one of them. For this post instead of going through the code step by step, i have added in-code comments, and will be letting the code speak for itself.

First things first, the permission.


The good thing is that we do not need to ask for this permission at runtime.

This project only has one activity, the MainActivity. Lets take a look at the code.

public class MainActivity extends AppCompatActivity {

    // Variable used for storing the key in the Android Keystore container
    private static final String KEY_STORE_ALIAS = "key_fingerprint";
    private static final String KEY_STORE = "AndroidKeyStore";

    private KeyStore keyStore;
    private Cipher cipher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Get an instance of the fingerprint manager through the getSystemService method
        final FingerprintManager fingerprintManager =
                (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);

        // Our fingerprint checker
        final FingerPrintChecker checker = new FingerPrintChecker(this, fingerprintManager);

        if (checker.isAbleToUseFingerPrint()) {
            generateAuthenticationKey();

            if (isCipherInitialized()) {
                // A wrapper for the crypto objects supported by the FingerprintManager
                final FingerprintManager.CryptoObject cryptoObject =
                        new FingerprintManager.CryptoObject(cipher);

                // Our fingerprint callback helper
                final FingerprintHelper fingerprintHelper = new FingerprintHelper(this);
                fingerprintHelper.startAuthentication(fingerprintManager, cryptoObject);
            }
        }
    }

    /**
     * Generates the authentication key required to use with the {@link FingerprintManager} to
     * encrypt/decrypt fingerprints.
     */
    @TargetApi (Build.VERSION_CODES.M)
    private void generateAuthenticationKey() {

        getKeyStoreInstance();

        final KeyGenerator keyGenerator = getKeyGenerator();

        try {
            keyStore.load(null);

            final KeyGenParameterSpec parameterSpec = getKeyGenParameterSpec();

            // Initialize the key generator
            keyGenerator.init(parameterSpec);

            // Generate the key. This also returns the generated key for immediate use if needed.
            // For this example we will grab it later on.
            keyGenerator.generateKey();

        } catch (NoSuchAlgorithmException |
                InvalidAlgorithmParameterException
                | CertificateException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Generate the {@link KeyGenParameterSpec} required for us to encrypt/decrypt.
     */
    @NonNull
    private KeyGenParameterSpec getKeyGenParameterSpec() {
        // Specify what we are trying to do with the generated key
        final int purposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;

        // Specifications for the key generator. How to generate the key
        return new KeyGenParameterSpec.Builder(KEY_STORE_ALIAS, purposes)
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setUserAuthenticationRequired(true)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build();
    }

    /**
     * Get an instance of the Java {@link KeyStore}
     */
    private void getKeyStoreInstance() {
        try {
            keyStore = KeyStore.getInstance(KEY_STORE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Get the key generator required to generate the keys uses for encryption/decryption
     */
    private KeyGenerator getKeyGenerator() {
        final KeyGenerator keyGenerator;

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

        return keyGenerator;
    }

    /**
     * Initializes the Cipher object required to perform the fingerprint authentication.
     *
     * @return True if Cipher init was successful. False otherwise.
     */
    @TargetApi (Build.VERSION_CODES.M)
    private boolean isCipherInitialized() {
        try {
            // Get a cipher instance with the following transformation --> AES/CBC/PKCS7Padding
            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 instance", e);
        }

        try {
            keyStore.load(null);

            // The key - This key was generated in the {@link #generateAuthenticationKey()} method
            final SecretKey key = (SecretKey) keyStore.getKey(KEY_STORE_ALIAS, 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);
        }
    }
}

There 2 helper classes used in the code above, lets start with the FingerPrintChecker.

class FingerPrintChecker {

    private final Context context;
    private final KeyguardManager keyguardManager;
    private final FingerprintManager fingerprintManager;

    FingerPrintChecker(final Context context, final FingerprintManager fingerprintManager) {
        this.context = context;
        this.fingerprintManager = fingerprintManager;

        // Initializing both Android Keyguard Manager and Fingerprint Manager
        keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
    }

    @SuppressWarnings ("MissingPermission")
    boolean isAbleToUseFingerPrint() {

        // Check whether the device has a Fingerprint sensor.
        if (!fingerprintManager.isHardwareDetected()) {
            // Device does not support fingerprint authentication. You can take this opportunity to
            // redirect the user to some other authentication method or activity
            showMessage("Your Device does not support fingerprint authentication");
            return false;
        } else {
            // Checks whether fingerprint permission is set
            if (isFingerprintPermissionEnabled()) {
                showMessage(String.format(context.getString(R.string.error_permission_missing),
                        Manifest.permission.USE_FINGERPRINT));
                return false;
            } else {
                // Check whether at least one fingerprint is registered
                if (!fingerprintManager.hasEnrolledFingerprints()) {
                    showMessage("Please register at least one fingerprint in your device settings");
                    return false;
                } else {
                    // Checks whether lock screen security is enabled or not
                    if (!keyguardManager.isKeyguardSecure()) {
                        showMessage("Lock screen security not enabled in your device settings");
                        return false;
                    } else {
                        return true;
                    }
                }
            }
        }
    }

    private boolean isFingerprintPermissionEnabled() {
        return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) !=
                PackageManager.PERMISSION_GRANTED;
    }

    private void showMessage(final String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
}

Our FingerPrintHelper class

class FingerprintHelper extends FingerprintManager.AuthenticationCallback {

    private static final String TAG = FingerprintHelper.class.getSimpleName();

    private final Context context;

    FingerprintHelper(@NonNull final Context context) {
        this.context = context;
    }

    void authenticate(@NonNull final FingerprintManager fingerprintManager,
                             @NonNull final FingerprintManager.CryptoObject cryptoObject) {

        // Provides the ability to cancel an operation in progress.
        final CancellationSignal cancellationSignal = new CancellationSignal();

        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) !=
                PackageManager.PERMISSION_GRANTED) {
            Log.e(TAG, "Error: cannot authenticate. Permission denied.");
            return;
        }

        /*
         Request authentication of a crypto object. This call warms up the fingerprint hardware
         and starts scanning for a fingerprint. It terminates when
         {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
         {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called,
         at which point the object is no longer valid. The operation can be canceled by using the
         provided cancel object.
         */
        fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
    }

    @Override
    public void onAuthenticationError(int errorCode, CharSequence errString) {
        showMessage("Fingerprint Authentication error" + errString);
    }

    @Override
    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
        showMessage("Fingerprint Authentication help" + helpString);
    }

    @Override
    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
        showMessage("Fingerprint Authentication succeeded.");
    }

    @Override
    public void onAuthenticationFailed() {
        showMessage("Fingerprint Authentication failed.");
    }

    private void showMessage(String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
}

You can find the full source code HERE

Thats all for now, until next time!

If you have any questions don’t hesitate to contact me or leave a comment!

2 Comments

  1. matt
    03/19/2017

    can u be so kind and post project code 🙂 ?
    thats great tutorial, but i have some problems with run it

Comments are closed.