studio intent how data android encryption android-keystore android-fingerprint-api

intent - Cifrado y descifrado de la API Android Fingerprint



get fingerprint data android (1)

Estoy usando la API Android M Fingerprint para permitir a los usuarios iniciar sesión en la aplicación. Para hacer esto, 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 API de huellas dactilares, 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 poder recuperarla después de que el usuario se autentique con su huella digital.

Tengo una gran cantidad de dificultades para lograr que esto funcione. He intentado aplicar lo que puedo de los ejemplos de Seguridad de Android , pero cada ejemplo parece que solo maneja el cifrado o la firma, y ​​nunca descifra.

Lo que tengo hasta ahora es que tengo que obtener una instancia de AndroidKeyStore , un KeyPairGenerator y un Cipher , usando una criptografía asimétrica para permitir el uso de Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true) . El motivo de la criptografía asimétrica se debe a que el método setUserAuthenticationRequired bloqueará el uso de la clave si el usuario no está autenticado, pero:

Esta autorización se aplica solo a las operaciones de 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 autentique con su huella digital, luego descifrarla usando la clave privada solo 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 sincero, no estoy seguro de si algo de esto es correcto, es fragmentos de todo lo que pude encontrar sobre el tema. Todo lo que cambio arroja una excepción diferente, y esta compilación en particular no se ejecuta porque no puedo crear una instancia del Cipher , arroja una NoSuchAlgorithmException: No provider found for EC . También intenté cambiar a RSA , pero recibo errores similares.

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

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

He mantenido getKeyStore , encryptePassword , decryptPassword , getKeyPairGenerator y getCipher su mayoría iguales, pero he cambiado 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 curva elíptica, porque según tengo entendido, Java 1.7 (y, por lo tanto, Android) no es compatible con el cifrado y el descifrado con EC. Cambié mi método createKeyPair basado en el ejemplo del "par de claves RSA para cifrado / descifrado con 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 initCipher mi método initCipher basado en el problema conocido en la documentación de KeyGenParameterSpec :

Un error conocido en Android 6.0 (nivel de API 23) hace que las autorizaciones relacionadas con la autenticación de usuario se apliquen incluso para las claves públicas. Para solucionar este problema, extraiga el material de clave pública para usar 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 encriptar la contraseña y guardar la contraseña encriptada. Pero cuando obtengo la contraseña encriptada e intento descifrar, recibo 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 Android Issue Tracker , otro error conocido hace que la PublicKey restringida sea incompatible con el Cipher cuando se usa OAEP. La OAEPParameterSpec es agregar un nuevo OAEPParameterSpec al Cipher al momento de la inicialización:

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

A continuación está 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); } }