Шифрование и расшифровка API отпечатков пальцев Android

я использую API отпечатков пальцев Android M, чтобы пользователи могли войти в приложение. Для этого мне нужно будет сохранить имя пользователя и пароль на устройстве. В настоящее время у меня работает логин, а также 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, он бросает!--10-->. Я попытался переключиться на RSA также, но я получаю аналогичные ошибки.

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


я добился некоторого прогресса, в основном благодаря открытию информации о KeyGenParameterSpec на странице документации.

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

я также изменил остальную часть кода на RSA вместо эллиптической кривой, потому что из того, что я понимаю, Java 1.7 (и поэтому Android) не поддерживает шифрование и дешифрование с помощью EC. Я изменил свой createKeyPair метод, основанный на примере "RSA key pair for encryption / decryption using 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.

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.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23.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.onAuthenticationSucceeded(FingerprintManagerCompat.java:301) 
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23.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)

1 ответов


Я нашел последний кусок головоломки на Android Проблема Tracker, еще одна известная ошибка приводит к неограниченному PublicKey быть несовместимым с Cipher при использовании УАМИАЭ. Работа вокруг, чтобы добавить новый 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);
    }
}