0

I am quite unsuccessfully trying to implement BiometricPrompt with authentication bound decryption key(without allowing pin/password/pattern alternatives). I am using asymmetric keys since I need to encrypt a string without user authentication and decrypt the string with user authentication required. However, when I try to use the cryptoObject provided by BiometricPrompt to the onAuthenticationSucceeded callback I get IllegalBlockSizeException nullcode = 100104. If I simply set the setUserAuthenticationRequired to false everything works just fine without exception. If there would be anything wrong with authentication wouldn't i get a UserNotAuthenticatedException? And if there would be anything wrong with encryption wouldn't I get the IllegalBlockSizeException no matter setUserAuthenticationRequired. What is the source of this IllegalBlockSizeException? and how can i solve it?

Encoding:

fun encode(
    keyAlias: String,
    decodedString: String,
    isAuthorizationRequired: Boolean
): String {
    val cipher: Cipher = getEncodeCipher(keyAlias, isAuthorizationRequired)
    val bytes: ByteArray = cipher.doFinal(decodedString.toByteArray())
    return Base64.encodeToString(bytes, Base64.NO_WRAP)
}

//allow encoding without user authentication
private fun getEncodeCipher(
    keyAlias: String,
    isAuthenticationRequired: Boolean
): Cipher {
    val cipher: Cipher = getCipherInstance()
    val keyStore: KeyStore = loadKeyStore()
    if (!keyStore.containsAlias(keyAlias))
        generateKey(keyAlias, isAuthenticationRequired
    )
     //from https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
    val key: PublicKey = keyStore.getCertificate(keyAlias).publicKey
    val unrestricted: PublicKey = KeyFactory.getInstance(key.algorithm).generatePublic(
            X509EncodedKeySpec(key.encoded)
        )
    val spec = OAEPParameterSpec(
        "SHA-256", "MGF1",
         MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT
    )
    cipher.init(Cipher.ENCRYPT_MODE, unrestricted, spec)
    return cipher
}

key generation:

private fun generateKey(keyAlias: String, isAuthenticationRequired: Boolean) {
    val keyGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore"
    )
    keyGenerator.initialize(
            KeyGenParameterSpec.Builder(
                keyAlias,
                KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
            ).run {
                setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                setUserAuthenticationRequired(isAuthenticationRequired) //only if isAuthenticationRequired is false -> No IllegalBlockSizeException during decryption
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    setInvalidatedByBiometricEnrollment(true)
                }
                build()
            }
        )
        keyGenerator.generateKeyPair()
    } 
}

Decode:

//this Cipher is passed to the BiometricPrompt
override fun getDecodeCipher(keyAlias: String): Cipher {
    val keyStore: KeyStore = loadKeyStore()
    val key: PrivateKey = keyStore.getKey(keyAlias, null) as PrivateKey
    cipher.init(Cipher.DECRYPT_MODE, key)
    return cipher
}

//this is called from inside onAuthenticationSucceeded BiometricPrompt.AuthenticationCallback
fun decodeWithDecoder(encodedStrings: List<String>, cryptoObject: BiometricPrompt.CryptoObject): List<String> {
    return try {
        encodedStrings.map {
            val bytes: ByteArray = Base64.decode(it, Base64.NO_WRAP)
            //here i get IllegalBlockSizeException after the first iteration if isAuthenticationRequired is set to true 
            String(cryptoObject.cipher!!.doFinal(bytes)) 
        }
 
}

BiometricPrompt:

 private fun setUpBiometricPrompt() {
        executor = ContextCompat.getMainExecutor(requireContext())

        val callback = object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                Log.d("${this.javaClass.canonicalName}", "onAuthenticationError $errString")

            }

            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                Log.d("${this.javaClass.canonicalName}", "Authentication failed")
            }

            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
               //passing the received crypto object on for decryption operation (which then fails) 
                decodeWithDecoder(encodedString: String, result.cryptoObject)
                super.onAuthenticationSucceeded(result)
            }
        }

        promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Biometric login for my app")
            .setSubtitle("Log in using your biometric credential")
            .setNegativeButtonText("Use account password")
            .build()

        biometricPrompt = BiometricPrompt(this, executor, callback)
    }
    
    //show the prompt
    fun authenticate() {
        biometricPrompt.authenticate(
        promptInfo, 
        BiometricPrompt.CryptoObject(getDecodeCipher()))
    }
Macs
  • 197
  • 2
  • 15

1 Answers1

0

I realized that a rather important detail was missing from my initial code samples. in reality i tried to use the cipher inside cryptoObec for multiple encryption operations. I added that detail to the samples above since that was obviously the cause of the exception. So the answer is that obviously how one sets setUserAuthenticationRequired on the key affects how often once can use the (onetime) initialized cipher object. with set to false you can use is multiple times and with set to true only once. Or am I missing something here? the question still remains of course how I can decrypt multiple strings with user authentication bound keys? which others had similar problems with Android - Use Fingerprint scanner and Cipher to encrypt and decrypt multiple strings

Macs
  • 197
  • 2
  • 15
  • If you want to add additional information to your question, then you should do so by editing the question rather than posting the new information as an answer. – Michael Jul 10 '20 at 09:10
  • As far as performing multiple cryptographic operations after a single authentication is concerned, see [setUserAuthenticationValidityDurationSeconds](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds(int)). – Michael Jul 10 '20 at 09:13
  • sorry for that and thanks for your answer. I thought i provided the answer for the problem I stated above and the question of how often I can use a the crypto object after authentication is basically a new one. i mentioned that i do not want to allow for pin/password/pattern alternatives only biometrics. hence setUserAuthenticationValidityDurationSeconds is not really an option. i need to encode multiple strings which combined are > 256 bytes though. – Macs Jul 10 '20 at 21:32