Android: PoC Biometric Authentication

Hello!

Welcome to another Android-related Proof-of-Concept app!

Today I would like to focus on a vastly significant cybersecurity process, which is authentication!

The Android platform supports a variety of users identify proving, so without further ado, let’s check those related to biometrics!

Android User Credentials

In this simple PoC app, I would like to emphasize on build-in authentication options.

As we all know, we have a few authentication factors:

  • Something you have – a physical object like card, token, key, etc.,
  • Something you know – PIN, password, TAN,
  • Something you are – physical characteristics – biometrics, such as fingerprint, eye iris, voice, behavior,
  • Somewhere you are – location.

Android gives us opportunities to use all of them – I’ll love to explain it in more details in the future, so if you want to check it out, here is @placeholder for a link, but we have easy-to-use build in features to:

  • Use biometric credentials (something you are)
  • we can invoke device credential checking. (something you know)

Till Android 28 (Android 9), we had a dedicated FingerprintManager, and it was only supporting fingerprint (what a surprise 😉 ).

Since Android 28, we have BiometricPrompt and BiometricManager to manage these features, and I’m going to concentrate on those classes – so the newest possible solution to handle biometric authentication.

The Biometric library expands upon the functionality of the deprecated FingerprintManager API.

Show a Biometric Dialog – Android Developers

Concept & User Interface

With possibilities given by the platform, we can:

  • Use biometric fingerprint authentication pop-up (Android 28-29) (fingerprint or cancel)
  • Use biometric fingerprint authentication or device credentials (for Android 29) (fingerprint or device creds)
  • Use biometric authentication both fingerprint or face auth or device credentials (Android 30 only) (fingerprint or device creds or face auth)

Since I do not have Android 30 yet, I’ll stick to the first two options. 😉

Basically, I’m going to build a straightforward UI that will contain:

Two buttons – to invoke biometric pop-up – with and without device credentials.

Also, I would like to show some stats – authentication successes, and setbacks – failures and errors (this I’ll increment both when: authentication failed – creds were wrong/fingerprint not recognized and when the user will cancel the prompt)

Before we start playing around with biometrics and device cred, best practices tell us that we should check if they exist 😊.

So I’ll add those parameters checking

  • we will check with KeyguardManager the option isDeviceSecured() – returns true or false
  • and with BiometricManager if the device canAuthenticate() equals to BiometricManager.BIOMETRIC_SUCCESS (to get true or false)

My simple as possible layout can is not the most beautiful:

My simple UI

But it does the trick. Below you can find XML:

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

    <Button
        android:id="@+id/btW"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="300dp"
        android:layout_marginEnd="100dp"
        android:text="With"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btWO"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="300dp"
        android:text="Without"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <TextView
        android:id="@+id/tvStats"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="400dp"
        android:layout_marginEnd="175dp"
        android:text="Successes"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvParams"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="50dp"
        android:layout_marginTop="100dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Backend & config

Having UI in place, let’s go under the surface.

First, we need to change our build.gradle(:app). In the default config, it’s crucial to set correct minSdkVersion:

minSdkVersion 29
targetSdkVersion 29

Also, because we are going to use androidx:biometrics, we have to add the additional dependency:

dependencies {
…  
implementation 'androidx.biometric:biometric:1.0.1'
…
}

And we are good to go 😊 Time to create our MainActivity!

We are going to need some variables, so I’ll declare them in one place: executor is just required, then one biometric prompt with two biometric prompt infos (one for “with device credentials” and second for “without device credentials”), biometric manager, keyguard, integers for stats and view variables.

public class MainActivity extends AppCompatActivity {

    private Executor executor;
    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;
    private BiometricPrompt.PromptInfo promptInfoWO;
    private BiometricManager manager;
    private KeyguardManager keyguard;
    private int successes;
    private int errors;
    TextView tvStats;
    TextView tvParams;
    Button btLoginWith;
    Button btLoginWithout;

And the magic takes place in the onCreate() method. First, we need to assign values/create objects:

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btLoginWith = findViewById(R.id.btW);
        btLoginWithout = findViewById(R.id.btWO);
        tvStats = findViewById(R.id.tvStats);
        tvParams = findViewById(R.id.tvParams);

        successes = 0;
        errors = 0;

        manager = manager.from(this);
        keyguard = (KeyguardManager) this.getSystemService(KEYGUARD_SERVICE);
        tvStats.setText("Successes: "  + 
successes + "\nErrors: " + errors);
        tvParams.setText("can authenticate: " 
+String.valueOf(
manager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) +
                "\nis device secure: " 
+  String.valueOf(keyguard.isDeviceSecure())  );

        executor = ContextCompat.getMainExecutor(this);

Then we will create a BiometricPrompt object with the required logic:

biometricPrompt = new BiometricPrompt(MainActivity.this,
                executor, new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode,
                                              @NonNull CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                Toast.makeText(getApplicationContext(),
                        
"Authentication error: " + errString, Toast.LENGTH_SHORT)
                        .show();
                errors += 1;
                tvStats.setText("Successes: "  
+ successes + "\nErrors: " + errors);
            }

            @Override
            public void onAuthenticationSucceeded(
                    @NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                Toast.makeText(getApplicationContext(),
                        
"Authentication succeeded!", Toast.LENGTH_SHORT).show();
                successes +=1;
                tvStats.setText("Successes: "  
+ successes + "\nErrors: " + errors);

            }

            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                Toast.makeText(getApplicationContext(), 
"Authentication failed",
                        Toast.LENGTH_SHORT)
                        .show();
                errors += 1;
                tvStats.setText("Successes: "  
+ successes + "\nErrors: " + errors);

            }
        });

Mine is heavily based on Show a Biometric Dialog – Android Developers training complemented with stats refreshment .

Then I’m building to proptInfos – with and without credentials:

promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login with device credentials")
                .setSubtitle("Use biometric or device credentials")
                .setDeviceCredentialAllowed(true)
                .build();

        promptInfoWO = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login without device credentials")
                .setSubtitle("Use biometric credentials only")
                .setDeviceCredentialAllowed(false)
                .setNegativeButtonText("Cancel")
                .build();

Please note the difference – if DeviceCredentials are not allowed, we have to add NegativeButtonText (Here: “Cancel”)

The last what we need to do is to assign OnClickListeners for out Buttons:

btLoginWith.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                biometricPrompt.authenticate(promptInfo);
            }
        });

btLoginWithout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                biometricPrompt.authenticate(promptInfoWO);
            }
        });
    }
} # end of MainActivity

Please bear in mind that if you are using Android 30 and above, you can change the code into:

// Allows user to authenticate using either a "strong" hardware element or
// their lock screen credential (PIN, pattern, or password).
promptInfo = new BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        // Can't call setNegativeButtonText() and
        // setAllowedAuthenticators(...|DEVICE_CREDENTIAL) at the same time.
        // .setNegativeButtonText("Use account password")
        .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
        .build();

Source: Show a Biometric Dialog – Android Developers

And customize allowed authenticators for your needs:

  • BIOMETRIC_STRONG – Authentication using a hardware element that satisfies the Strong strength level (Fingerprint)
  • BIOMETRIC_WEAK – Authentication using a hardware element that satisfies the Weak (face auth)
  • DEVICE_CREDENTIAL – Authentication using a screen lock credential
  • And combined them with | operator (e.g., BIOMETRIC_STRONG | DEVICE_CREDENTIAL, BIOMETRIC_STRONG | BIOMETRIC_WEAK, DEVICE_CREDENTIAL (alone) ) and so one.

Results & Conclusion

The application runs as expected:

When I’m trying to authenticate without user credentials, prompt shows and I have to use my fingerprint:

Biometric propmpt – fingerprint only

After checking the second option (with user credentials), I can provide both user credentials (pattern in my case) or fingerprint.

And, what shocks, I’m not able to do the screenshot!

Biometric prompt with user credentials – OnePlus 6 (OxygenOS 10.3.5)

I’m actually satisfied with this little PoC application – it’ll bring value in further projects, and I’ll be able to use it (and do not describe again) almost ‘as is.’. It also triggered the idea for another article about all authentication factors on the Android platform (stay tuned).

Source code for this project on my Github: HeadFullOfCiphers/AndroidPoCBiometricAuth

That it for today 😊 if you liked this post, have any suggestions, questions, or just want to contact me – do not hesitate!

Reference list:

  1. Show a Biometric Dialog – Android Developers
  2. Androidx:biometrics – Android Developers.
  3. KeyguardManager – Android Developers.
  4. BiometricManager – Android Developers.

Check out related posts:

3 thoughts on “Android: PoC Biometric Authentication

Add yours

  1. I’d be interested in pros and cons of the presented authentication methods. What level of security do they really give us?
    Which one should we trust more?

    Looking forward to new content!

    Like

    1. Kamil, thanks a lot for a comment! According to documentation, fingerprint method is described as BIOMETRIC_STRONG. It’s related with FAR vs FRR comparison.
      I’ll elaborate in further arts – stay tuned!

      Like

Leave a comment

Blog at WordPress.com.

Up ↑