I am developing an android app which uses fingerprint/face recognition for unlocking the app.
I have successfully integrated fingerprint authentication using BiometricPrompt. But didn't know where to start for Face authentication. Any headsup will be really helpful.
Also, Since BiometricPrompt comes with face, fingerprint, and iris, I don't want to use either MLKIT or any third party libraries.
Below is the piece of code I used for Fingerprint authentication.
new BiometricPrompt
.Builder(context)
.setTitle(title)
.setSubtitle(subtitle)
.setDescription(description)
.setNegativeButton(negativeButtonText, context.getMainExecutor(),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
biometricCallback.onAuthenticationCancelled();
}
})
.build()
.authenticate(new CancellationSignal(), context.getMainExecutor(),
new BiometricCallbackV28(biometricCallback));
Android 10: Will allow both finger print and Face ID as currently in Samsung s10.
Android 9: Will allow only Fingerprint authentication (irrespective of face Id unlock exist or not )
reference link https://source.android.com/security/biometric/#source
Edit1: However the Samsung is not following google's ritual. Samsung will still have different behaviours .
1.Samsung - Android 9 - you can choose preferred biometrics and this applicable for the apps well.
But there is a bug. If you disable the finger print authentication for device level unlock you will get prompted for the fingerprint authentication in the app level.
Short summary :
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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With