I haven’t written an article for a long time, and I have been lazy recently. Today, I will continue to discuss the needs I met in the actual development, that is, APP unlocking, as we all know. Now more and more apps will not re-enter the account password after the second login after entering the account number and password. But fast login, and commonly used is fingerprint unlock and gesture unlock two.


All right, let’s get started on today’s unlock tour.

Here I just show the logic of my requirements. Different projects may have different logic, which does not affect the main content of this article.

The main steps are divided into three steps:

  1. Login using the account and password. After successful login, a dialog box is displayed asking the user to select a shortcut login mode.
  2. Then jump to the corresponding quick login Settings screen
  3. Make a quick login the next time you log in

Let’s take it step by step.

Select a shortcut login mode

After successful login with the account and password, a pop-up box will pop up on the login interface, and then let the user choose the desired quick login method. Of course, if the user does not want either of the two, he can press Cancel directly, and then log in to the home page, and then he will have to re-enter the account and password next time he opens the application.

Quick login method selection box


Here comes our first question:

Since there are many types of Android phones, some have fingerprints and some don’t, we need to pop out of the pop-up box with two options when we have fingerprints. If there is no fingerprint unlock, we can directly jump to the interface of gesture unlock.

My judgment may be more general, but there are better ones:

  1. I directly judge whether SDK >= 23, because fingerprint unlock comes from SDK 23, but many domestic mobile phones may be Android 5 system, but they also have fingerprint unlock. I’m just going to ignore that. Don’t blame me for being cruel.
  2. Use reflection to retrieve a FingerprintManager object in your Application. If so, store a Boolean variable true, indicating that there is a fingerprint related object in this phone. If it fails, there are no fingerprints.

    public class MyApplication extends Application {
     public static final String HAS_FINGERPRINT_API = "hasFingerPrintApi";
     public static final String SETTINGS = "settings";
    
     @Override
     public void onCreate() {
         super.onCreate();
         SharedPreferences sp = getSharedPreferences(SETTINGS, MODE_PRIVATE);
         if (sp.contains(HAS_FINGERPRINT_API)) { // Check for the presence of the value, not every time through reflection
             return;
         }
         SharedPreferences.Editor editor = sp.edit();
         try {
             Class.forName("android.hardware.fingerprint.FingerprintManager"); // Check whether the class exists by reflection
             editor.putBoolean(HAS_FINGERPRINT_API, true);
         } catch (ClassNotFoundException e) {
             editor.putBoolean(HAS_FINGERPRINT_API, false); e.printStackTrace(); } editor.apply(); }}Copy the code

Once we’ve worked out the timing of the popup, we’re going to make this popup:

I used to make pop-ups using Dialog series. Later, I accidentally saw that Google recommended people to use DialogFragment to make pop-ups instead of the original Dialog, so I just took this opportunity to write this DialogFragment myself. I will give only the important parts below. Specific we go to Baidu DialogFragment can be.

public class LockChooseFragment extends DialogFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setRetainInstance(true);

        // Set the DialogFragment theme and the Style of the dialog box.
        setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Material_Light_Dialog);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_lock_choose, container, false);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        aty = (LoginActivity) getActivity();

        // Make our popbox unable to click on the outside area disappear
        getDialog().setCanceledOnTouchOutside(false);
        getDialog().setCancelable(false); }}Copy the code

All right. Then the popup box comes out and you have to click on one unlock, and then go to the next screen. Let’s start with a simple gesture to unlock.


Gestures to unlock

I used Github’s open source gesture for unlocking: PatternLockView

Lol isn’t it too easy… Don’t blame me for being lazy. Because the GIthub API is clearly written. I’m not going to go over how to use it. I used it and found it was really good. Recommended.

Gestures to unlock


Fingerprint unlock



The first thing we know is that Google provides the Fingerprint package. The classes below the package are as follows:

  1. FingerprintManager: Used to coordinate management and access fingerprint hardware devices
  2. FingerprintManager AuthenticationCallback this a callback interface, when the fingerprint authentication system callback the interface to inform what’s the result of the app certification
  3. FingerprintManager. AuthenticationResult said this is a class certification results, will be in the callback interface parameters are given
  4. FingerprintManager CryptoObject this is an encrypted object class, to ensure the security of authentication.

Before we get started, we need to know the basic steps for using fingerprint recognition hardware:

  1. Gets the object reference to FingerprintManager

  2. This section describes how to check the compatibility of fingerprint identification devices, such as fingerprint identification devices.

Let’s break down steps 2 and 3 above:

Gets the object reference to FingerprintManager

This is the common way to obtain system service objects in APP development, as follows:

// Using the Android Support Library v4
fingerprintManager = FingerprintManagerCompat.from(this);
// Using API level 23:
fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);Copy the code

There are two ways to do this. The first is to get compatible object references through the V4 support pack, which is what Google is pushing. Another option is to get object references directly using interfaces in the API 23 framework.


This section describes how to check the compatibility of fingerprint identification devices, such as fingerprint identification devices

Check operating Conditions For our fingerprint recognition app to work properly, there are certain conditions that must be met.

  1. API Level 23 Fingerprint recognition API was added in API Level 23 (Android 6.0), so our app must run on this system version. Therefore, Google recommends using the Android Support Library v4 package to get FingerprintManagerCompat objects, since the package checks the current system platform version when obtained.
  2. Hardware fingerprint recognition definitely requires that your device has fingerprint recognition hardware, so you need to check whether the system has fingerprint recognition hardware at runtime:

Using fingerprintManager. IsHardwareDetected () to determine whether to have the hardware support, fingerprintManager. HasEnrolledFingerprints () to determine whether mobile phone the same fingerprint.

Here I ran into a big pit when using my phone for development, as mentioned above. Google recommends using FingerprintManagerCompat, but when I call isHardwareDetected() and hasEnrolledFingerprints() with FingerprintManagerCompat, Both return false, but when I call isHardwareDetected() and hasEnrolledFingerprints() with FingerprintManager it returns true, actually I’m using Xiaomi 5, Android 6, API23 does have a fingerprint feature, so I don’t know why FingerprintManagerCompat returns a problem. I also have a lot of people in Google Issue Tracker who have this problem. But basically what huawei, Xiaomi, Samsung, etc., are not the son of Google. So I used the FingerprintManager class, which is required to be above API23, because after all, Google’s fingerprint is derived from API23, and I directly judged API23 to show the fingerprint unlock option. Happen to coincide. Ha ha. Maybe it’s a little lazy here.

After determining whether there is hardware support and whether the phone has a fingerprint, be aware that Google also needs to determine that the device must be secured, meaning that your device must be protected with a screen lock, which can be a password, PIN or pattern. Why is that? Google’s native logic is that in order to use fingerprint recognition, you must first enable screen lock, which is the same logic as Smart Lock in Android 5.0. This is because Google believes that the current fingerprint recognition technology still has some shortcomings and security is not as good as traditional methods.

KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); 
if (keyguardManager.isKeyguardSecure()) { 
// this device is secure. 
}Copy the code

So the conclusion here is:

  1. Whether the device is supported by hardware
  2. Whether the mobile phone is under security protection (prompt the user to turn on the lock screen if it is not)
  3. Whether the phone has a fingerprint (if not, prompt the user to add a fingerprint to the Settings app)

All right, with all this foreplay done, we’re about to start fingerprint verification.

Validation of the fingerprint

It’s easy to start scanning the user’s pressed fingerprint by calling the Authenticate method of FingerprintManager, so now let’s look at the interface:

  1. Crypto is a cryptographic object that fingerprint scanners use to determine the validity of an authentication result. This object can be null, but this means that the app trusts the result of the authentication unconditionally, although in theory the process can be attacked and the data can be tampered with, which is the risk that the app must bear in this case. Therefore, it is recommended not to set this parameter to NULL. This class is a bit cumbersome to instantiate and is implemented primarily using javax’s Security interface.
  2. Cancel This object is an object of the CancellationSignal class. This object is used to cancel the scan while the fingerprint scanner is scanning the user’s fingerprint. If not, the fingerprint scanner will migrate the scan until it times out (typically 30 seconds, depending on the vendor implementation). This will be more power intensive. Do not set this parameter to null.
  3. Flags flag bit. According to the diagram’s documentation, this bit should be 0 for the time being. This flag bit should be reserved for future use.
  4. The callback. This is FingerprintManager AuthenticationCallback objects of a class, this is the interface in addition to the first parameter is the most important parameters. When the system completes the fingerprint authentication process (either a failure or a success), it calls back the interface in this object and notifies the APP of the authentication result. This parameter cannot be NULL.
  5. Handler This is the object of the Handler class. If this parameter is not null, FingerprintManager will use the looper in this handler to process messages from the fingerprint recognition hardware. In general, developers do not provide this parameter and can set it to NULL because FingerprintManager will use the app’s main looper by default.

According to the above parameters, we make specific analysis one by one:

Create the CryptoObject class object

When we analyze authenticate method of FingerprintManager, we see that the first argument of the authenticate method is the object of the CryptoObject class. Now let’s look at how to instantiate this object. We know that the reliability of the results of fingerprint identification is very important, and we certainly don’t want the authentication process to be attacked in some way by a third party, because the purpose of introducing fingerprint authentication is to improve security. However, from a theoretical perspective, the fingerprint authentication process can be maliciously attacked by third-party middleware, the common means of attack is to intercept and tamper with the results provided by the fingerprint reader. Here we can provide a CryptoObject object to authenticate to avoid this form of attack. FingerprintManager CryptoObject is based on a Java encryption API wrapper classes, and by FingerprintManager used to ensure the integrity of the certification results. In general, the mechanism used to encrypt fingerprint scan results is a Javax.crypto.Cipher object. Cipher objects themselves generate a key using the API that the application calls the Android Keystore to implement the protection described above. To understand how these classes work together, I present a wrapper class code that instantiates the CryptoObject object. Let’s first look at how this code is implemented and then explain why it is so.

public class CryptoObjectHelper
{
    // This can be key name you want. Should be unique for the app.
    static final String KEY_NAME = "com.createchance.android.sample.fingerprint_authentication_key";

    // We always use this keystore on Android.
    static final String KEYSTORE_NAME = "AndroidKeyStore";

    // Should be no need to change these values.
    static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
    static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
    static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
    static final String TRANSFORMATION = KEY_ALGORITHM + "/" +
    BLOCK_MODE + "/" +
    ENCRYPTION_PADDING;
    final KeyStore _keystore;

    public CryptoObjectHelper() throws Exception
    {
        _keystore = KeyStore.getInstance(KEYSTORE_NAME);
        _keystore.load(null);
    }

    public FingerprintManagerCompat.CryptoObject buildCryptoObject() throws Exception
    {
        Cipher cipher = createCipher(true);
        return new FingerprintManagerCompat.CryptoObject(cipher);
    }

    Cipher createCipher(boolean retry) throws Exception
    {
        Key key = GetKey();
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        try
        {
            cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key);
        } catch(KeyPermanentlyInvalidatedException e)
        {
            _keystore.deleteEntry(KEY_NAME);
            if(retry)
            {
                createCipher(false);
            } else
            {
                throw new Exception("Could not create the cipher for fingerprint authentication.", e); }}return cipher;
    }

    Key GetKey() throws Exception
    {
        Key secretKey;
        if(! _keystore.isKeyEntry(KEY_NAME)) { CreateKey(); } secretKey = _keystore.getKey(KEY_NAME,null);
        return secretKey;
    }

    void CreateKey() throws Exception
    {
        KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME);
        KeyGenParameterSpec keyGenSpec =
                new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(BLOCK_MODE)
                        .setEncryptionPaddings(ENCRYPTION_PADDING)
                        .setUserAuthenticationRequired(true) .build(); keyGen.init(keyGenSpec); keyGen.generateKey(); }}Copy the code

The above class creates a new Cipher object for each CryptoObject object, using the key generated by the application. The name of this key is defined using the KEY_NAME variable. This name should be guaranteed to be unique. Domain name differentiation is recommended. The GetKey method attempts to use the Android Keystore API to parse a key (the name we defined above), and call CreateKey to create a new key if the key doesn’t exist. A cipher variable is instantiated by calling cipher.getInstance, which takes a transformation parameter that specifies how data is encrypted and decrypted. The Cipher. Init method is then called to instantiate the Cipher object using the application key. It is important to note that Android considers the current key invalid in the following cases:

  1. A new fingerprint image has been registered with the system
  2. Previously registered fingerprints on the current device do not exist and may have been deleted altogether
  3. The user disabled the screen lock function
  4. Users to change the way the screen lock When the above happens, Cipher. The init method will throw KeyPermanentlyInvalidatedException abnormalities, I caught the exception in the code above, and delete the current invalid key, Then try to create it again based on the parameters. The above code uses Android’s KeyGenerator to create a key and store it on the device. The KeyGenerator class creates a key, but some raw data is required to create the key. This raw information is provided through the object of the KeyGenParameterSpec class. The instantiation of the KeyGenerator class object is done using its factory method getInstance. As you can see from the above code, the Advanced Encryption Standard (AES) Encryption algorithm is used to divide the data into several groups. It is then encrypted for several groups. Next, KeyGenParameterSpec instantiation is to use its Builder method, KeyGenParameterSpec. Builder encapsulates the following important information:
  5. The name of the key
  6. The key must be valid for encryption and decryption
  7. BLOCK_MODE is set to Cipher Block Chaining, also known as keyproperties.block_mode_cbc, which means that each AES Block is xOR with the previous Block. The goal is to establish dependencies between each data block.
  8. The CryptoObjectHelper class uses PKSC7 (Public Key Cryptography Standard #7) to generate bytes used to fill AES blocks. This is to ensure that the size of each data block is equal (because xOR calculation and aspect algorithm are required for data processing, see AES algorithm principle for details).
  9. SetUserAuthenticationRequired (true) calls means before using the key user identity need to be certified. Each time a KeyGenParameterSpec is created, it is used to initialize the KeyGenerator, which generates a key stored on the device.

How do I use CryptoObjectHelper?

Here’s how to use the CryptoObjectHelper class:

CryptoObjectHelper cryptoObjectHelper = new CryptoObjectHelper();
fingerprintManager.authenticate(cryptoObjectHelper.buildCryptoObject(), 0,cancellationSignal, myAuthCallback, null);Copy the code

Using CryptoObjectHelper is relatively simple, first new a CryptoObjectHelper object, and then call buildCryptoObject to get the CryptoObject object.


Canceling Fingerprint Scanning

We mentioned above that canceling the fingerprint scan is quite common. CancellationSignal CancellationSignal CancellationSignal CancellationSignal






Process user fingerprint authentication results

Front we analyze authenticate interface said, call this interface must be provided when FingerprintManager. AuthenticationCallback class object, the object will be at the end of the fingerprint authentication system after the callback to notify the app certification results. In Android 6.0, fingerprint scanning and authentication are completed in another process (fingerprint system service), so it is not possible to assume when the bottom layer can complete authentication in our app. Therefore, we can only adopt asynchronous mode of operation, when the system is completed at the bottom of the time take the initiative to inform us, inform the way is through our own implementation FingerprintManager callback. AuthenticationCallback class, This class defines some callback methods for us to do the necessary processing:


  1. OnAuthenticationError (int errorCode, ICharSequence errString) this interface will only be called if the system fingerprint authentication has an unrecoverable error, and the errorCode parameter gives the errorCode, The cause of the error was identified. All the app can do is prompt the user to try again.
  2. OnAuthenticationFailed() This interface will only be called back if the system fingerprint authentication fails. Note that the authentication failure here is not the same as the authentication error above, although the result is not authentication. Authentication failure means that all information is collected completely and there is no exception, but the fingerprint is inconsistent with the fingerprint registered before. Authentication errors refer to errors during collection or authentication, for example, fingerprint sensors work incorrectly. In other words, authentication failure is an expected normal condition, while authentication error is an unexpected exception.
  3. The authentication failure on OnAuthenticationHelp(int helpMsgId, ICharSequence helpString) is an exception in the authentication process, and we say that because there is an unrecoverable error, Our OnAuthenticationHelp method is called only when there is an exception that can be recovered. What are recoverable exceptions? A common example is that a finger moves too fast. If we remove the finger quickly when we place it on the sensor, the fingerprint sensor may only pick up partial information, so authentication will fail. But the error is recoverable, so it can be fixed simply by prompting the user to press the fingerprint again and not remove it too quickly.
  4. OnAuthenticationSucceeded (FingerprintManagerCompati AuthenticationResult result), the interface will callback after successful authentication. We can prompt the user for successful authentication in this method. It should be noted here that if our CryptoObject is not null when we call authenticate above, then we can get the Cypher object through AuthenticationResult and call its doFinal method in this method. The doFinal method checks to see if the result has been intercepted or tampered with, and throws an exception if so. We should treat authentication as a failure when we find these exceptions, as is recommended for safety.

    There are two more things to add about the interface above:
    1. We said above that the OnAuthenticationError and OnAuthenticationHelp methods have errors or help codes to indicate why authentication failed. The Android system defines several errors and help codes in the FingerprintManager class, as follows:



      Our callback class should be implemented to handle these errors and help codes.

    2. When the fingerprint scanner is working, if we cancel the operation, the system will also callback OnAuthenticationError method, just at this time of the error code is FingerprintManager FINGERPRINT_ERROR_CANCELED (value for 5), So apps need to be treated differently.

For example, here’s my custom AuthenticationCallback class that I wrote

 class FingerAuthCallback extends FingerprintManagerCompat.AuthenticationCallback {

        @Override
        public void onAuthenticationError(int errMsgId, CharSequence errString) {
            super.onAuthenticationError(errMsgId, errString);
            showError(errString);
        }

        @Override
        public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
            super.onAuthenticationHelp(helpMsgId, helpString);
            showError(helpString);
        }

        @Override
        public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);


            mIcon.setImageResource(R.drawable.ic_fingerprint_success);
            mErrorTextView.setTextColor(
                    ContextCompat.getColor(context, R.color.success_color));
            mErrorTextView.setText(
                    context.getResources().getString(R.string.fingerprint_success));


        }

        @Override
        public void onAuthenticationFailed() {
            super.onAuthenticationFailed(); showError(context.getResources().getString(R.string.fingerprint_not_recognized)); }}Copy the code

For additional

Fingerprint unlocking can be done using this open source library on Github: FingerprintAuthHelper I used. At least I got it right.

Google’s Fingerprint unlocked Demo: FingerprintDialog (click the Download button in the upper right corner to download the Demo)


Thanks to Createchance for the Android 6.0 Fingerprint recognition App demo