Я использую API Android Fingerprint API, чтобы разрешить пользователям входить в приложение. Для этого мне нужно будет сохранить имя пользователя и пароль на устройстве. В настоящее время у меня работает логин, а также API отпечатков пальцев, но имя пользователя и пароль хранятся в виде открытого текста. Я хотел бы зашифровать пароль, прежде чем я его сохраню, и иметь возможность получить его после того, как пользователь будет аутентифицироваться с помощью отпечатка пальца.
У меня много трудностей с тем, чтобы это работало. Я пытаюсь применить все, что могу, из образцов безопасности Android, но каждый пример, похоже, обрабатывает только шифрование или подпись и никогда не расшифровывается.
До сих пор я должен был получить экземпляр AndroidKeyStore
, a 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) заставляет авторизацию, связанную с аутентификацией пользователя, применяться даже для открытых ключей. Чтобы обойти эту проблему, извлеките материал открытых ключей для использования за пределами Keystore Android.
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)