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