Почему шифрование с привязкой к аутентификации Android BiometricPrompt выдает исключение IllegalBlockSizeException

Я довольно безуспешно пытаюсь реализовать BiometricPrompt с ключом дешифрования, связанным с аутентификацией (без использования альтернативных вариантов PIN / пароля / шаблона). Я использую асимметричные ключи, так как мне нужно зашифровать строку без аутентификации пользователя и расшифровать строку с необходимой аутентификацией пользователя. Однако, когда я пытаюсь использовать cryptoObject, предоставленный BiometricPrompt, для обратного вызова onAuthenticationSucceeded, я получаю IllegalBlockSizeException nullcode = 100104. Если я просто устанавливаю для setUserAuthenticationRequired значение false, все работает нормально без исключения. Если что-то не так с аутентификацией, разве я не получу UserNotAuthenticatedException? И если с шифрованием что-то не так, я бы не получил исключение IllegalBlockSizeException независимо от setUserAuthenticationRequired. Каков источник этого исключения IllegalBlockSizeException? и как я могу это решить?

Кодировка:

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
}

генерация ключей:

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()
    } 
}

Расшифровать:

//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)) 
        }
 
}

Биометрический

 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()))
    }



Ответы (1)


Я понял, что в моих первоначальных образцах кода не хватало довольно важной детали. на самом деле я пытался использовать шифр внутри cryptoObec для нескольких операций шифрования. Я добавил эту деталь к примерам выше, поскольку это, очевидно, было причиной исключения. Итак, ответ заключается в том, что, очевидно, то, как один устанавливает setUserAuthenticationRequired для ключа, влияет на то, как часто один раз можно использовать (одноразовый) инициализированный объект шифра. со значением false вы можете использовать несколько раз, а со значением true - только один раз. Или мне что-то здесь не хватает? вопрос, конечно, все еще остается, как я могу расшифровать несколько строк с привязанными ключами аутентификации пользователя? у каких других были аналогичные проблемы с Android - Используйте сканер отпечатков пальцев и шифр для шифрования и дешифрования нескольких строк

person Macs    schedule 09.07.2020
comment
Если вы хотите добавить дополнительную информацию к своему вопросу, вам следует сделать это, отредактировав вопрос, а не размещать новую информацию в качестве ответа. - person Michael; 10.07.2020
comment
Что касается выполнения нескольких криптографических операций после одной аутентификации, см. setUserAuthenticationValidityDurationSeconds. - person Michael; 10.07.2020
comment
извините за это и спасибо за ваш ответ. Я думал, что дал ответ на проблему, о которой говорил выше, и вопрос о том, как часто я могу использовать крипто-объект после аутентификации, в основном новый. Я упомянул, что не хочу разрешать только биометрические альтернативы пин-код / ​​пароль / шаблон. следовательно, setUserAuthenticationValidityDurationSeconds на самом деле не вариант. мне нужно закодировать несколько строк, которые вместе составляют ›256 байтов. - person Macs; 11.07.2020