UserNotAuthenticatedException во время FingerprintManager.authenticate()

У меня есть зашифрованный пароль, хранящийся в Android KeyStore.

Я хочу расшифровать этот пароль, аутентифицировав пользователя с помощью API отпечатков пальцев.

Насколько я понимаю, мне нужно вызвать метод FingerprintManager.authenticate(CryptoObject cryptoObject), чтобы начать прослушивание результата отпечатка пальца. Параметр CryptoObject создается следующим образом:

public static Cipher getDecryptionCipher(Context context) throws KeyStoreException {
    try {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKey secretKey = getKeyFromKeyStore();
        final IvParameterSpec ivParameterSpec = getIvParameterSpec(context);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        return cipher;

    } catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | UnrecoverableKeyException | CertificateException | InvalidAlgorithmParameterException | InvalidKeyException e) {
        e.printStackTrace();

    }

    return null;
}

Cipher cipher = FingerprintCryptoHelper.getDecryptionCipher(getContext());
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, ...);

Метод getDecryptionCipher() работает корректно до вызова cipher.init(). На этом вызове я получаю UserNotAuthenticatedException, потому что пользователь не аутентифицирован для этого secretKey. Это имеет смысл как-то. Но это не цикл, который невозможно выполнить:

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

Что здесь не так?

EDIT:

Я работаю с эмулятором (Nexus 4, API 23).

Вот код, который я использую для создания ключа.

private SecretKey createKey() {
    try {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
        keyGenerator.init(new KeyGenParameterSpec.Builder(
                KEY_NAME,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
        )
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build());
        return keyGenerator.generateKey();
    } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
        throw new RuntimeException("Failed to create a symmetric key", e);
    }
}

Ответ 1

Оказалось, что проблема связана с известной проблемой с KeyGenParameterSpec, которая запрещает использование открытого ключа без аутентификации (что именно то, что не нужно публичному ключу).

Соответствующий вопрос/ответ можно найти здесь: Шифрование и дешифрование API отпечатков пальцев Android

Обходной путь - создать PublicKey из первоначально созданного ключа и использовать этот неограниченный PublicKey для инициализации шифрования. Поэтому мой последний шифр использует AES/CBC/PKCS7Padding и инициализируется с помощью этого метода:

public boolean initCipher(int opMode) {
    try {
        Key key = mKeyStore.getKey(KEY_NAME, null);

        if (opMode == Cipher.ENCRYPT_MODE) {
            final byte[] encoded = key.getEncoded();
            final String algorithm = key.getAlgorithm();
            final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
            PublicKey unrestricted = KeyFactory.getInstance(algorithm).generatePublic(keySpec);

            mCipher.init(opMode, unrestricted);

        } else {
            final IvParameterSpec ivParameterSpec = getIvParameterSpec();
            mCipher.init(opMode, key, ivParameterSpec);

        }

        return true;

    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;

    } catch ( NoSuchAlgorithmException | InvalidKeyException
            | InvalidKeySpecException | InvalidAlgorithmParameterException | UnrecoverableKeyException | KeyStoreException exception) {
        throw new RuntimeException("Failed to initialize Cipher or Key: ", exception);
    }
}

@NonNull
public IvParameterSpec getIvParameterSpec() {
    // the IV is stored in the Preferences after encoding.
    String base64EncryptionIv = PreferenceHelper.getEncryptionIv(mContext);
    byte[] encryptionIv = Base64.decode(base64EncryptionIv, Base64.DEFAULT);
    return new IvParameterSpec(encryptionIv);
}