32

Я использую API отпечатков пальцев Android M, чтобы пользователи могли войти в приложение. Для этого мне нужно будет сохранить имя пользователя и пароль на устройстве. В настоящее время у меня работает логин, а также API отпечатков пальцев, но имя пользователя и пароль хранятся в виде открытого текста. Я хотел бы зашифровать пароль, прежде чем я его сохраню, и иметь возможность получить его после того, как пользователь будет аутентифицироваться с помощью отпечатка пальца.Шифрование и дешифрование API отпечатков пальцев Android

У меня возникли трудности с тем, чтобы это работало. Я пытаюсь применить то, что могу, от Android Security samples, но каждый пример, похоже, обрабатывает только шифрование или подписание и никогда не расшифровывает.

То, что я до сих пор является то, что я должен получить экземпляр AndroidKeyStore, в KeyPairGenerator и Cipher, используя асимметричную криптографию, чтобы позволить использование Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true). Причина асимметричной криптографии, так как метод setUserAuthenticationRequired будет блокировать любого использования ключа, если пользователь не аутентифицирован, но:

Это разрешение относится только к секретному ключу и секретным ключ операциям. Операции с открытым ключом не ограничены.

Это должно позволить мне зашифровать пароль с использованием открытого ключа до того, как пользователь будет аутентифицироваться с помощью отпечатка пальца, а затем дешифровать с помощью закрытого ключа только после аутентификации пользователя.

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

Честно говоря, я не уверен, если это будет правильно, это биты и куски от всего, что я мог найти на эту тему. Все, что я меняю, вызывает другое исключение, и эта конкретная сборка не выполняется, потому что я не могу создать экземпляр Cipher, он выбрасывает NoSuchAlgorithmException: No provider found for EC. Я также попытался переключиться на RSA, но получаю аналогичные ошибки.

Так что мой вопрос в основном такой; как я могу зашифровать открытый текст на Android и сделать его доступным для дешифрования после того, как пользователь будет аутентифицирован API-интерфейсом Fingerprint?


Я сделал некоторые успехи, в основном, в связи с открытием информации на странице KeyGenParameterSpec документации.

Я сохранил getKeyStore, encryptePassword, decryptPassword, getKeyPairGenerator и getCipher в основном то же самое, но я изменил KeyPairGenerator.getInstance и Cipher.getInstance к "RSA" и "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" соответственно.

Я также поменял остальную часть кода на RSA вместо Elliptic Curve, потому что из того, что я понимаю, Java 1.7 (и, следовательно, Android) не поддерживает шифрование и дешифрование с помощью EC. Я изменил свой createKeyPair метод, основанный на «пары ключей RSA для шифрования/дешифрования с использованием RSA OAEP» примера на странице документации:

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

Я также изменил мой initCipher метод, основанный на известной проблеме в документации KeyGenParameterSpec :

Известная ошибка в Android 6.0 (API-уровень 23) заставляет авторизацию, связанную с аутентификацией пользователя, применяться даже для открытых ключей. Чтобы обойти эту проблему, извлеките материал открытого ключа для использования вне 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); 
    } 
} 

Теперь я могу зашифровать пароль и сохранить зашифрованный пароль. Но когда я получить зашифрованный пароль и попытку расшифровать, я получаю 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) 
+0

Эй вы сможете создать суть для полного кода, необходимого, чтобы сделать это? Я искал решение для этого около 2 недель. – MichaelStoddart

+0

@ TheAndroidDev. Честно говоря, я не очень хорошо отделял код UI от кода шифрования/дешифрования, а код полагался на Dagger и RxJava, поэтому легко многоразовый метод не может быть тривиальным. Я посмотрю, что я могу придумать. Но на данный момент большая часть кода (без кинжала) находится в моем другом вопросе: [Как использовать Unsupported Exception для более поздней версии платформы] (http://stackoverflow.com/q/37553650/5115932). – Bryan

+0

вы могли бы ответить на мой вопрос? http://stackoverflow.com/questions/40724749/how-to-get-key-from-keystore-on-successful-fingerprint-auth – MichaelStoddart

ответ

28

я нашел последний кусочек головоломки на Android Issue Tracker, другая известная ошибка приводит к неограниченным PublicKey к быть несовместимым с Cipher при использовании OAEP. Обходной добавить новый OAEPParameterSpec к Cipher при инициализации:

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

mCipher.init(opmode, unrestricted, spec); 

Ниже приведен окончательный код:

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

Привет, я попробовал гораздо больше кода, и один из них - ваш код. Я получаю сообщение об ошибке «Crypto primitive not initialized». Можете ли вы успешно запустить код? – atasoyh

+0

@atasoyh Да, я использовал этот код. Ошибка, которую вы получаете, кажется, говорит, что 'Cipher' не инициализируется. Вы действительно вызываете 'initCipher()', прежде чем пытаетесь его использовать? – Bryan

+0

Да, я назвал initChipper. Я перешел на реализацию AES. Сейчас я работаю с этим. – atasoyh

Смежные вопросы