Java.lang.IllegalArgumentException: учетные данные устройства не поддерживаются криптографией

Я пытаюсь настроить BiometricPrompt, но мне нужна аутентификация с помощью CryptoObject, что кажется невозможным, когда для https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder.html#setDeviceCredentialAllowed (логическое значение) установлено значение true.

try {
      KeyGeneratorUtil.generateKeyPair("1", null);
    } catch (Exception e) {
      e.printStackTrace();
    }

    PrivateKey privateKey;
    try {
      privateKey = KeyGeneratorUtil.getPrivateKeyReference("test");
    } catch (Exception e) {
      return;
    }

    final Signature signature;
    try {
      signature = initSignature(privateKey);
    } catch (Exception e) {
      return;
    }
final BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(signature);

final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(context)
        .setTitle("Title")
        .setDescription("Description")
        .setDeviceCredentialAllowed(true)
        .build();

...

biometricPrompt.authenticate(cryptoObject, new CancellationSignal(), executor, callback);

Когда я запускаю это, я получаю следующее исключение.

2019-07-03 13:50:45.140 13715-13715/kcvetano.com.biometricpromptpoc E/AndroidRuntime: FATAL EXCEPTION: main
    Process: kcvetano.com.biometricpromptpoc, PID: 13715
    java.lang.IllegalArgumentException: Device credential not supported with crypto
        at android.hardware.biometrics.BiometricPrompt.authenticate(BiometricPrompt.java:556)
        at kcvetano.com.biometricpromptpoc.BiometryAPI29.handleBiometry(BiometryAPI29.java:65)
        at kcvetano.com.biometricpromptpoc.MainActivity$1.onClick(MainActivity.java:56)
        at android.view.View.performClick(View.java:7251)
        at android.view.View.performClickInternal(View.java:7228)
        at android.view.View.access$3500(View.java:802)
        at android.view.View$PerformClick.run(View.java:27843)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7116)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:925)

что такое криптообъект

Manoj Perumarath 03.07.2019 13:57

Пожалуйста, добавьте часть, где вы создаете этот объект

Manoj Perumarath 03.07.2019 14:00

В настоящее время это не поддерживается. Я понятия не имею, будет ли он поддерживаться в конечном итоге. На данный момент вам придется выбрать один или другой.

Michael 08.08.2019 13:04
2
4
3 414
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это должно помочь:

biometricPrompt.authenticate(null, new CancellationSignal(), executor, callback);

В сообщении об ошибке более-менее написано (можно сказать скрытый): при использовании setDeviceCredentialAllowed(true) не использовать криптообъект.

Все сводится к тому, как настроен ваш закрытый ключ для криптооперации внутри CryptoObject.

Я предполагаю, что ваш закрытый ключ, используемый для инициализации объекта подписи, создан с помощью setUserAuthenticationRequired(true). Ключи, созданные с этой опцией, предназначены для использования только для одной операции шифрования. Кроме того, их необходимо разблокировать с помощью биометрии, используя либо BiometricPrompt.authenticate, либо FingerprintManager.authenticate (теперь это устарело в пользу BiometricPrompt).

официальная документация говорит о двух режимах, если ключи разрешены для использования только в том случае, если пользователь прошел аутентификацию, то есть:

  • Ключи, созданные с помощью setUserAuthenticationRequired(true), должны быть разблокированы с помощью FingerprintManager.authenticate (теперь BiometricPrompt.authenticate)
  • Ключи, созданные с помощью setUserAuthenticationValidityDurationSeconds, должны быть разблокированы с помощью потока KeyguardManager.createConfirmDeviceCredentialIntent.

Примечание в конце официальное учебное пособие по биометрической аутентификации предлагает переключиться с KeyguardManager.createConfirmDeviceCredentialIntent потока на новый BiometricPrompt с setDeviceCredentialAllowed(true).

Но это не так просто, как установить для UserAuthenticationValidityDuration ключа ненулевое значение, поскольку это вызовет UserNotAuthenticatedException внутри вашего initSignature(privateKey) вызова, как только инициализируется объект подписи. И есть еще несколько предостережений... См. два примера ниже.


Авторизация по биометрическому ключу

fun biometric_auth() {

    val myKeyStore = KeyStore.getInstance("AndroidKeyStore")
    myKeyStore.load(null)

    val keyGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
    )

    // build MY_BIOMETRIC_KEY
    val keyAlias = "MY_BIOMETRIC_KEY"
    val keyProperties = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
    val builder = KeyGenParameterSpec.Builder(keyAlias, keyProperties)
        .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
        .setDigests(KeyProperties.DIGEST_SHA256)
        .setUserAuthenticationRequired(true)


    keyGenerator.run {
        initialize(builder.build())
        generateKeyPair()
    }

    val biometricKeyEntry: KeyStore.Entry = myKeyStore.getEntry(keyAlias, null)
    if (biometricKeyEntry !is KeyStore.PrivateKeyEntry) {
        return
    }

    // create signature object
    val signature = Signature.getInstance("SHA256withECDSA")
    // init signature else "IllegalStateException: Crypto primitive not initialized" is thrown
    signature.initSign(biometricKeyEntry.privateKey)
    val cryptoObject = BiometricPrompt.CryptoObject(signature)

    // create biometric prompt
    // NOTE: using androidx.biometric.BiometricPrompt here
    val prompt = BiometricPrompt(
        this,
        AsyncTask.THREAD_POOL_EXECUTOR,
        object : BiometricPrompt.AuthenticationCallback() {
            // override the required methods...
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                Log.w(TAG, "onAuthenticationError $errorCode $errString")
            }

            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                super.onAuthenticationSucceeded(result)
                Log.d(TAG, "onAuthenticationSucceeded" + result.cryptoObject)
                val sigBytes = signature.run {
                    update("hello world".toByteArray())
                    sign()
                }
                Log.d(TAG, "sigStr " + Base64.encodeToString(sigBytes, 0))
            }

            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                Log.w(TAG, "onAuthenticationFailed")
            }
        })
    val promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Unlock your device")
        .setSubtitle("Please authenticate to ...")
        // negative button option required for biometric auth
        .setNegativeButtonText("Cancel")
        .build()
    prompt.authenticate(promptInfo, cryptoObject)
}


PIN-код/пароль/шаблон аутентификации

fun password_auth() {

    val myKeyStore = KeyStore.getInstance("AndroidKeyStore")
    myKeyStore.load(null)

    val keyGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
    )

    // build MY_PIN_PASSWORD_PATTERN_KEY
    val keyAlias = "MY_PIN_PASSWORD_PATTERN_KEY"
    val keyProperties = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
    val builder = KeyGenParameterSpec.Builder(keyAlias, keyProperties)
        .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
        .setDigests(KeyProperties.DIGEST_SHA256)
        // this would trigger an UserNotAuthenticatedException: User not authenticated when using the fingerprint
        // .setUserAuthenticationRequired(true)
        .setUserAuthenticationValidityDurationSeconds(10)


    keyGenerator.run {
        initialize(builder.build())
        generateKeyPair()
    }

    val keyEntry: KeyStore.Entry = myKeyStore.getEntry(keyAlias, null)
    if (keyEntry !is KeyStore.PrivateKeyEntry) {
        return
    }

    // create signature object
    val signature = Signature.getInstance("SHA256withECDSA")
    // this would fail with UserNotAuthenticatedException: User not authenticated
    // signature.initSign(keyEntry.privateKey)

    // create biometric prompt
    // NOTE: using androidx.biometric.BiometricPrompt here
    val prompt = BiometricPrompt(
        this,
        AsyncTask.THREAD_POOL_EXECUTOR,
        object : BiometricPrompt.AuthenticationCallback() {
            // override the required methods...
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                Log.w(TAG, "onAuthenticationError $errorCode $errString")
            }

            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                super.onAuthenticationSucceeded(result)
                Log.d(TAG, "onAuthenticationSucceeded " + result.cryptoObject)
                // now it's safe to init the signature using the password key
                signature.initSign(keyEntry.privateKey)
                val sigBytes = signature.run {
                    update("hello password/pin/pattern".toByteArray())
                    sign()
                }
                Log.d(TAG, "sigStr " + Base64.encodeToString(sigBytes, 0))
            }

            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                Log.w(TAG, "onAuthenticationFailed")
            }
        })
    val promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Unlock your device")
        .setDeviceCredentialAllowed(true)
        .build()
    prompt.authenticate(promptInfo)
}

Вы можете найти небольшое демо здесь: github.com/simne7/BiometricAuthApp

simne7 16.10.2019 16:45

Этот ответ не следует принимать как действительный, если вы не делаете .setUserAuthenticationRequired(true) в сочетании с .setUserAuthenticationValidityDurationSeconds(10), он на самом деле ничего не делает. Все, что вы сделали, это создали ключ, который не требует аутентификации, вы можете убедиться в этом, не разблокируя блокировку клавиатуры, а просто используя ключ в своем примере с PIN-кодом.

martin treurnicht 14.11.2019 01:29

Приведенный выше ответ не очень точен как в отношении решения, так и в отношении объяснения.

Чтобы использовать как биометрическую аутентификацию, так и учетные данные устройства вместе с криптообъектом, выполните следующие действия:

  1. Создайте секретный ключ с помощью setUserAuthenticationRequired(true) и setUserAuthenticationValidityDurationSeconds(x).
private SecretKey createSecretKey(String keyName ){
  KeyGenParameterSpec.Builder paramsBuilder = new KeyGenParameterSpec.Builder(keyName,
                    KeyProperties.PURPOSE_SIGN);
            paramsBuilder.setUserAuthenticationRequired(true);
            paramsBuilder.setUserAuthenticationValidityDurationSeconds(5);
            KeyGenParameterSpec keyGenParams = paramsBuilder.build();
            KeyGenerator keyGenerator = null;
           keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_HMAC_SHA256,
                        ANDROID_KEYSTORE);

            keyGenerator.init(keyGenParams);
            return keyGenerator.generateKey();
       }// All exceptions unhandled

  1. Инициализировать криптообъект
Mac mac=Mac.getInstance("HmacSHA256");
SecretKey secretKey = getOrCreateSecretKey(keyName);
mac.init(secretKey);

3. Используйте биометрическую аутентификацию с помощью setDeviceCredentialAllowed(true). Не передавайте параметр криптообъекта в метод аутентификации - вот так - biometricPrompt.authenticate(promptInfo)

Под onAuthenticationSucceeded

public void onAuthenticationSucceeded(
                    @NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                byte[] bytes = "secret-text".getBytes();
                byte[] macResult = mac.doFinal(bytes);
                Log.d("hashed data--",bytesToHex(macResult));
            }

Объект Mac будет работать только в том случае, если устройство было разблокировано не более x секунд назад. (setUserAuthenticationValidityDurationSeconds(x)).

Вы можете попробовать использовать объект Mac вне метода onAuthSucceeded после x секунд разблокировки устройства для проверки . Обратите внимание, что даже разблокировка телефона сделает объект Mac доступным для использования в течение x секунд. Его не нужно разблокировать внутри вашего приложения.

Подробнее об этом здесь: https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05f-testing-local-authentication

Другие вопросы по теме