UserNotAuthenticatedException durante FingerprintManager.authenticate ()

Tengo una contraseña cifrada almacenada en el KeyStore de Android.

Quiero descifrar esa contraseña autenticando al usuario usando la API de huellas dactilares.

Por lo que entiendo, tengo que llamar al método FingerprintManager.authenticate(CryptoObject cryptoObject) para comenzar a escuchar el resultado de la huella digital. El parámetro CryptoObject se crea así:

 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, ...); 

El método getDecryptionCipher() funciona correctamente hasta la llamada cipher.init() . En esta llamada recibo una UserNotAuthenticatedException , porque el usuario no está autenticado para este secretKey. Que tiene sentido de alguna manera. Pero, ¿no es esto un bucle, imposible de cumplir:

  • Para autenticar al usuario, quiero usar su huella digital
  • Para escuchar su huella dactilar, necesito init el Cipher, que a cambio necesita un usuario autenticado

¿Qué hay de malo aquí?

EDITAR:

Trabajo con el emulador (Nexus 4, API 23).

Aquí está el código que utilizo para crear la clave.

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

Resultó que el problema estaba relacionado con un problema conocido con KeyGenParameterSpec que impide el uso de la clave pública sin autenticación (que es exactamente lo que la clave pública NO necesita).

Una pregunta / respuesta relacionada puede encontrarse aquí: Android Fingerprint API Encryption and Decryption

La solución es crear una PublicKey desde la clave creada originalmente y utilizar esta clave pública sin restricciones para init el cifrado. Así que mi cifrado final utiliza AES / CBC / PKCS7Padding y se inicializa con este método:

 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); } 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.