12

I have problem with customization biometricPrompt in android devices. I use only authorization with fingerprint, but some devices with android 9.0 (for example Samsung Galaxy S10+) for authorization use fingerprint if allowed but the same with Facial authentication. If user allowed both facial & fingerprint authentization biometricPrompt use for authentization facial recognition. I need allow only fingerprint, if user not allowed fingerprint but facial yes i need block it.

Documentation told me this (from docs) enter image description here

... but without any directions and i can't find anything about customization in source codes.

my code for launch authentication dialog is here

 BiometricPrompt.Builder(context)
                    .setTitle(biometricBuilder.title ?: "")
                    .setSubtitle(biometricBuilder.subtitle ?: "")
                    .setDescription(biometricBuilder.description ?: "")
                    .setNegativeButton(biometricBuilder.negativeButtonText ?: "",
                            context.mainExecutor, DialogInterface.OnClickListener { dialogInterface, i -> biometricCallback.onAuthenticationCancelled() })
                    .build()
                    .authenticate(CancellationSignal(), context.mainExecutor,
                            BiometricCallbackV28(biometricCallback))

Thanks for any help

Parad0X
  • 3,540
  • 2
  • 9
  • 22
  • 1
    This is unfortunately not supported. The documentation your refer to is for device manufacturers, not app developers. All it says is that _end users_ should be able to manually select their preferred biometric in the Settings app. – Michael Mar 26 '19 at 08:54
  • so when i want only fingerprint is not able with BiometricPrompt? – Parad0X Mar 26 '19 at 08:56
  • 1
    Not at the moment. I [filed an issue](https://issuetracker.google.com/issues/111315641) about that last year, but haven't received any meaningful response yet. – Michael Mar 26 '19 at 08:58
  • and you have some resolution for this problem(prefer one authentization and block second) or use both (fingerprint and facial) – Parad0X Mar 26 '19 at 09:03
  • 2
    Currently there is no resolution when using `BiometricPrompt`. If you want to only allow fingerprints you can use `FingerprintManager` (it's deprecated, but that doesn't mean it has been removed). – Michael Mar 26 '19 at 09:05
  • ok, thanks. problem solved. – Parad0X Mar 26 '19 at 09:27
  • any updates on this ? – Cosmic Dev Jun 06 '20 at 05:52

1 Answers1

0

A bit late, but I hope this answer could help other developers. I was also trying to achieve the same thing and ended up with a simple solution:

Supply a crypto object when calling .authenticate() like this:

/**
 * Prerequisites:
 * 1. Add
 * `implementation "androidx.biometric:biometric:1.0.1"` in build.gradle
 * 2. Add
 * `    <uses-permission android:name="android.permission.USE_BIOMETRIC" android:requiredFeature="false"/>`
 * in AndroidManifest.xml
 */
object BiometricHelper {
    private const val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
    private const val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
    private const val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
    private const val KEY_SIZE = 128

    private lateinit var biometricPrompt: BiometricPrompt

    fun authenticate(fragmentActivity: FragmentActivity, authCallback: BiometricPrompt.AuthenticationCallback){
        try {
            if (!fragmentActivity.supportFragmentManager.executePendingTransactions()) {
                biometricPrompt = createBiometricPrompt(fragmentActivity, authCallback)
                val promptInfo = createPromptInfo()
                biometricPrompt.authenticate(
                    promptInfo,
                    cryptoObject //Providing crypto object here will block Iris and Face Scan
                )
            }
        }
        catch (e: KeyPermanentlyInvalidatedException) {
            e.printStackTrace()
        }
        catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun createBiometricPrompt(fragmentActivity: FragmentActivity, authCallback: BiometricPrompt.AuthenticationCallback): BiometricPrompt {
        val executor = ContextCompat.getMainExecutor(fragmentActivity)
        return BiometricPrompt(fragmentActivity,  executor, authCallback)
    }

    private fun createPromptInfo(): BiometricPrompt.PromptInfo {
        return BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authentication")
            .setConfirmationRequired(false)
            .setNegativeButtonText("Cancel")
            .setDeviceCredentialAllowed(false) //Don't Allow PIN/pattern/password authentication.
            .build()
    }
    //endregion


    //====================================================================================
    //region Dummy crypto object that is used just to block Face, Iris scan
    //====================================================================================
    /**
     * Crypto object requires STRONG biometric methods, and currently Android considers only
     * FingerPrint auth is STRONG enough. Therefore, providing a crypto object while calling
     * [androidx.biometric.BiometricPrompt.authenticate] will block Face and Iris Scan methods
     */
    private val cryptoObject by lazy {
        getDummyCryptoObject()
    }

    private fun getDummyCryptoObject(): BiometricPrompt.CryptoObject {
        val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"
        val cipher = Cipher.getInstance(transformation)
        var secKey = getOrCreateSecretKey(false)
        try {
            cipher.init(Cipher.ENCRYPT_MODE, secKey)
        }
        catch (e: KeyPermanentlyInvalidatedException) {
            e.printStackTrace()
            secKey = getOrCreateSecretKey(true)
            cipher.init(Cipher.ENCRYPT_MODE, secKey)
        }
        catch (e: Exception) {
            e.printStackTrace()
        }
        return BiometricPrompt.CryptoObject(cipher)
    }

    private fun getOrCreateSecretKey(mustCreateNew: Boolean): SecretKey {
        val keyStore = KeyStore.getInstance("AndroidKeyStore")
        keyStore.load(null)
        if (!mustCreateNew) {
            keyStore.getKey("dummyKey", null)?.let { return it as SecretKey }
        }

        val paramsBuilder = KeyGenParameterSpec.Builder("dummyKey",
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        paramsBuilder.apply {
            setBlockModes(ENCRYPTION_BLOCK_MODE)
            setEncryptionPaddings(ENCRYPTION_PADDING)
            setKeySize(KEY_SIZE)
            setUserAuthenticationRequired(true)
        }

        val keyGenParams = paramsBuilder.build()
        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
            "AndroidKeyStore")
        keyGenerator.init(keyGenParams)
        return keyGenerator.generateKey()
    }
    //endregion
}

Gist

EDITED: This solution will work only if Face scan and/or Iris scan authentications on that device are considered WEAK methods.

  • 2
    `//Providing crypto object here will block Iris and Face Scan` There's no guarantee of that. It all depends on which of the device's biometric sensors are classified as STRONG. – Michael Jun 11 '20 at 11:45
  • That's true. I forgot to mention that. – Pisal UTNGY Jun 12 '20 at 06:37