Step1: Add the latest biometric dependency in build.gradle file.
implementation 'androidx.biometric:biometric:1.0.1'
Step2: Add permission for biometric in AndroidManifest.xml file.
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
Step3: Create activity_login.xml file and define the Touch ID button's UI.
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="@+id/parent_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="30dp">
<Button
android:id="@+id/touch_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/river_blue"
android:stateListAnimator="@null"
android:text="Biometric Login"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp" />
</RelativeLayout>
</layout>
Step4: Create LoginActivity.java file to implement the biometric login feature.
LoginActivity.java:
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import android.os.Build;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.widget.Button;
import androidx.biometric.BiometricManager;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
/**
* Created by: rajoo.kannaujiya on 02/16/2020
*/
@RequiresApi(api = Build.VERSION_CODES.P)
public class LoginActivity extends AppCompatActivity {
private static final String KEY_NAME = "KeyName";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String FORWARD_SLASH = "/";
private Button touchButton;
@RequiresApi(api = Build.VERSION_CODES.P)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
touchButton = findViewById(R.id.touch_button);
touchButton.setOnClickListener((view) -> onTouchIdClick());
displayBiometricButton();
}
private void onTouchIdClick() {
getBiometricPromptHandler().authenticate(getBiometricPrompt(), new BiometricPrompt.CryptoObject(getCipher()));
// Please see the below mentioned note section.
// getBiometricPromptHandler().authenticate(getBiometricPrompt());
}
private boolean isBiometricCompatibleDevice() {
if (getBiometricManager().canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
return true;
} else {
return false;
}
}
private void displayBiometricButton() {
if (isBiometricCompatibleDevice()) {
touchButton.setEnabled(false);
} else {
touchButton.setEnabled(true);
generateSecretKey();
}
}
private BiometricManager getBiometricManager() {
return BiometricManager.from(this);
}
private void generateSecretKey() {
KeyGenerator keyGenerator = null;
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(false)
.build();
try {
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
e.printStackTrace();
}
if (keyGenerator != null) {
try {
keyGenerator.init(keyGenParameterSpec);
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
keyGenerator.generateKey();
}
}
private SecretKey getSecretKey() {
KeyStore keyStore = null;
Key secretKey = null;
try {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
} catch (KeyStoreException e) {
e.printStackTrace();
}
if (keyStore != null) {
try {
keyStore.load(null);
} catch (CertificateException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
try {
secretKey = keyStore.getKey(KEY_NAME, null);
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
e.printStackTrace();
}
}
return (SecretKey) secretKey;
}
private Cipher getCipher() {
Cipher cipher = null;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + FORWARD_SLASH
+ KeyProperties.BLOCK_MODE_CBC + FORWARD_SLASH
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
try {
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
} catch (InvalidKeyException e) {
e.printStackTrace();
}
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
return cipher;
}
private BiometricPrompt.PromptInfo getBiometricPrompt() {
return new BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric login for my app")
.setSubtitle("Login with your biometric credential")
.setNegativeButtonText("cancel")
.setConfirmationRequired(false)
.build();
}
private void onBiometricSuccess() {
//Call the respective API on biometric success
callLoginApi("userName", "password");
}
private BiometricPrompt getBiometricPromptHandler() {
return new BiometricPrompt(this, ContextCompat.getMainExecutor(this),
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
onBiometricSuccess();
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
}
);
}
}
Note: The authentication level for biometric sensors (fingerprint, face, iris) are classified as Strong and Weak. As per Android Compatibility Definition Document (Android CDD), To access the android key store, the authentication level for biometric sensors must be classified as Strong. Since face and iris sensor are classified as Weak category in some of the devices, Hence in those devices face and iris options will not be displayed in the biometric prompt authenticated with CryptoObject.
getBiometricPromptHandler().authenticate(getBiometricPrompt(), new BiometricPrompt.CryptoObject(getCipher()));
If you authenticate the biometric prompt without CryptoObject, Then it will be displayed.
getBiometricPromptHandler().authenticate(getBiometricPrompt());