Cifrado y descifrado de Android Fingerprint API

Estoy utilizando la API de huellas dactilares de Android M para permitir a los usuarios iniciar sesión en la aplicación. Para ello necesitaría almacenar el nombre de usuario y la contraseña en el dispositivo. Actualmente tengo el inicio de sesión funcionando, así como la Fingerprint API, pero el nombre de usuario y la contraseña se almacenan como texto sin formato. Me gustaría cifrar la contraseña antes de almacenarla, y ser capaz de recuperarla después de que el usuario se autentica con su huella digital.

Estoy teniendo una gran cantidad de dificultad para que esto funcione. He estado tratando de aplicar lo que puedo de las muestras de seguridad de Android , pero cada ejemplo parece manejar sólo el cifrado o la firma, y ​​nunca descifrar.

Lo que tengo hasta ahora es que tengo que obtener una instancia de la AndroidKeyStore , un KeyPairGenerator y un Cipher , utilizando criptografía asimétrica para permitir el uso de Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true) . La razón de la criptografía asimétrica es porque el método setUserAuthenticationRequired bloqueará cualquier uso de la clave si el usuario no está autenticado, pero:

Esta autorización se aplica únicamente a las operaciones con clave secreta y clave privada. Las operaciones de clave pública no están restringidas.

Esto debería permitirme encriptar la contraseña usando la clave pública antes de que el usuario se autentica con su huella dactilar, luego descifrar utilizando la clave privada sólo después de que el usuario sea autenticado.

 public KeyStore getKeyStore() { try { return KeyStore.getInstance("AndroidKeyStore"); } catch (KeyStoreException exception) { throw new RuntimeException("Failed to get an instance of KeyStore", exception); } } public KeyPairGenerator getKeyPairGenerator() { try { return KeyPairGenerator.getInstance("EC", "AndroidKeyStore"); } catch(NoSuchAlgorithmException | NoSuchProviderException exception) { throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception); } } public Cipher getCipher() { try { return Cipher.getInstance("EC"); } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) { throw new RuntimeException("Failed to get an instance of Cipher", exception); } } private void createKey() { try { mKeyPairGenerator.initialize( new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1") .setUserAuthenticationRequired(true) .build()); mKeyPairGenerator.generateKeyPair(); } catch(InvalidAlgorithmParameterException exception) { throw new RuntimeException(exception); } } private boolean initCipher(int opmode) { try { mKeyStore.load(null); if(opmode == Cipher.ENCRYPT_MODE) { PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey(); mCipher.init(opmode, key); } else { PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null); mCipher.init(opmode, key); } return true; } catch (KeyPermanentlyInvalidatedException exception) { return false; } catch(KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException exception) { throw new RuntimeException("Failed to initialize Cipher", exception); } } private void encrypt(String password) { try { initCipher(Cipher.ENCRYPT_MODE); byte[] bytes = mCipher.doFinal(password.getBytes()); String encryptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP); mPreferences.getString("password").set(encryptedPassword); } catch(IllegalBlockSizeException | BadPaddingException exception) { throw new RuntimeException("Failed to encrypt password", exception); } } private String decryptPassword(Cipher cipher) { try { String encryptedPassword = mPreferences.getString("password").get(); byte[] bytes = Base64.decode(encryptedPassword, Base64.NO_WRAP); return new String(cipher.doFinal(bytes)); } catch (IllegalBlockSizeException | BadPaddingException exception) { throw new RuntimeException("Failed to decrypt password", exception); } } 

Para ser honesto, no estoy seguro de si algo de esto es correcto, es bits y piezas de todo lo que podría encontrar sobre el tema. Todo lo que cambia arroja una excepción diferente, y esta compilación en particular no se ejecuta porque no puedo instanciar el Cipher , que lanza una NoSuchAlgorithmException: No provider found for EC . He intentado cambiar a RSA también, pero consigo errores similares.

Así que mi pregunta es básicamente esto; ¿Cómo puedo cifrar el texto sin formato en Android y hacerlo disponible para el descifrado después de que el usuario sea autenticado por la API de huellas dactilares?


He hecho algunos progresos, principalmente debido al descubrimiento de la información en la página de documentación KeyGenParameterSpec .

He mantenido getKeyStore , encryptePassword , decryptPassword , getKeyPairGenerator y getCipher casi siempre lo mismo, pero he cambiado el KeyPairGenerator.getInstance y Cipher.getInstance a "RSA" y "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" respectivamente.

También cambié el resto del código a RSA en lugar de Elliptic Curve, porque de lo que yo entiendo, Java 1.7 (y por lo tanto Android) no soporta cifrado y descifrado con EC. Cambié mi método createKeyPair basado en el ejemplo "Par de claves RSA para encriptación / desencriptación utilizando RSA OAEP" en la página de documentación:

 private void createKeyPair() { try { mKeyPairGenerator.initialize( new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) .setUserAuthenticationRequired(true) .build()); mKeyPairGenerator.generateKeyPair(); } catch(InvalidAlgorithmParameterException exception) { throw new RuntimeException(exception); } } 

También modifiqué mi método initCipher basado en el problema conocido en la documentación de KeyGenParameterSpec :

Un error conocido en Android 6.0 (nivel 23 de API) hace que las autorizaciones relacionadas con la autenticación de usuario se apliquen incluso para claves públicas. Para evitar este problema extraer el material de clave pública para utilizar fuera de Android Keystore.

 private boolean initCipher(int opmode) { try { mKeyStore.load(null); if(opmode == Cipher.ENCRYPT_MODE) { PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey(); PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm()) .generatePublic(new X509EncodedKeySpec(key.getEncoded())); mCipher.init(opmode, unrestricted); } else { PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null); mCipher.init(opmode, key); } return true; } catch (KeyPermanentlyInvalidatedException exception) { return false; } catch(KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException exception) { throw new RuntimeException("Failed to initialize Cipher", exception); } } 

Ahora puedo cifrar la contraseña y guardar la contraseña cifrada. Pero cuando obtengo la contraseña cifrada e intento descifrar, consigo un error desconocido de KeyStoreException

 03-15 10:06:58.074 14702-14702/com.example.app E/LoginFragment: Failed to decrypt password javax.crypto.IllegalBlockSizeException at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:486) at javax.crypto.Cipher.doFinal(Cipher.java:1502) at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251) at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21) at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301) at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96) at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805) at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Caused by: android.security.KeyStoreException: Unknown error at android.security.KeyStore.getKeyStoreException(KeyStore.java:632) at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224) at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473) at javax.crypto.Cipher.doFinal(Cipher.java:1502) at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251) at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21) at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301) at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96) at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805) at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

Encontré la pieza final del rompecabezas en el Controlador de problemas de Android , otro error conocido hace que la PublicKey sin restricciones sea incompatible con el Cipher cuando se usa OAEP. El trabajo alrededor es agregar un nuevo OAEPParameterSpec al Cipher sobre la inicialización:

 OAEPParameterSpec spec = new OAEPParameterSpec( "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); mCipher.init(opmode, unrestricted, spec); 

A continuación se muestra el código final:

 public KeyStore getKeyStore() { try { return KeyStore.getInstance("AndroidKeyStore"); } catch (KeyStoreException exception) { throw new RuntimeException("Failed to get an instance of KeyStore", exception); } } public KeyPairGenerator getKeyPairGenerator() { try { return KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); } catch(NoSuchAlgorithmException | NoSuchProviderException exception) { throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception); } } public Cipher getCipher() { try { return Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) { throw new RuntimeException("Failed to get an instance of Cipher", exception); } } private void createKeyPair() { try { mKeyPairGenerator.initialize( new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) .setUserAuthenticationRequired(true) .build()); mKeyPairGenerator.generateKeyPair(); } catch(InvalidAlgorithmParameterException exception) { throw new RuntimeException("Failed to generate key pair", exception); } } private boolean initCipher(int opmode) { try { mKeyStore.load(null); if(opmode == Cipher.ENCRYPT_MODE) { PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey(); PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm()) .generatePublic(new X509EncodedKeySpec(key.getEncoded())); OAEPParameterSpec spec = new OAEPParameterSpec( "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); mCipher.init(opmode, unrestricted, spec); } else { PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null); mCipher.init(opmode, key); } return true; } catch (KeyPermanentlyInvalidatedException exception) { return false; } catch(KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException exception) { throw new RuntimeException("Failed to initialize Cipher", exception); } } private void encrypt(String password) { try { initCipher(Cipher.ENCRYPT_MODE); byte[] bytes = mCipher.doFinal(password.getBytes()); String encrypted = Base64.encodeToString(bytes, Base64.NO_WRAP); mPreferences.getString("password").set(encrypted); } catch(IllegalBlockSizeException | BadPaddingException exception) { throw new RuntimeException("Failed to encrypt password", exception); } } private String decrypt(Cipher cipher) { try { String encoded = mPreferences.getString("password").get(); byte[] bytes = Base64.decode(encoded, Base64.NO_WRAP); return new String(cipher.doFinal(bytes)); } catch (IllegalBlockSizeException | BadPaddingException exception) { throw new RuntimeException("Failed to decrypt password", exception); } } 
  • Perdí mi archivo de firma privada para Android
  • PKCS # 12 Creación de Keystore en el dispositivo Android
  • Android KeyStore System - Guardar un KeyPair?
  • Contrato Android desarrollador izquierda, la necesidad de actualizar la aplicación sin el keystore original - es posible?
  • ¿No se puede volver a crear el almacén de claves de depuración faltante?
  • ¿Podemos subir apk con diferentes keystore firmado después de desactivar el apk actual en android
  • Extraer certificado crudo X.509 de un firmado APK o JAR
  • SSLHandshakeException: Ancla de confianza para la ruta de certificación no encontrada. Solo en la API de Android <19
  • Android Keystore: "Keystore fue manipulado, o la contraseña era incorrecta."
  • Cómo agregar un alias a Android (Java) Keystore
  • Archivo de Keystore de Android perdido
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.