Инициализация объекта FingerpringManager.Crypto, получение примитива Crypto, не поддерживаемого поставщиком AndroidKeyStore?

Я использую Android FingerPrintManager API и создаю пару ключей с помощью KeyPairGenerator, я хочу зашифровать пароль с помощью открытого ключа, а затем расшифровать, когда пользователь аутентифицируется путем ввода fingerPrint, но как только я запускаю свой проект, он выходит из строя и дает

Вызвано: java.lang.IllegalArgumentException: примитив Crypto не поддерживается поставщиком AndroidKeyStore

я использовал код отсюда: Android Fingerprint API Encryption and Decryption в этом сообщении говорится что он может выполнять шифрование и дешифрование, и ди следовал тому же коду и шагам. вот мой код

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");
    } catch (NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKeyPair() {
    try {
        mKeyPairGenerator = getKeyPairGenerator();
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_NAME, 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);
    }
}

private boolean initCipher(int opmode) {
    try {
        mKeyStore = getKeyStore();
        mKeyStore.load(null);

        mCipher = getCipher();

        if (opmode == Cipher.ENCRYPT_MODE) {

            PublicKey key = mKeyStore.getCertificate(KEY_NAME).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_NAME, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch (KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | 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());
        enrcyptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP);
        Log.d("EncryptedText", enrcyptedPassword);
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decryptPassword(Cipher cipher) {
    try {
        initCipher(Cipher.DECRYPT_MODE);
        byte[] bytes = Base64.decode(enrcyptedPassword, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException | RuntimeException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}

и отсюда я инициализирую свой CryptoObject:

createKeyPair();
    if (initCipher(Cipher.ENCRYPT_MODE)) {
        mCryptoObject = new FingerprintManager.CryptoObject
                (mCipher);
        encrypt("1111");
        if (!isFingerprintAuthAvailable()) {
            return;
        }
        mCancellationSignal = new CancellationSignal();
        mSelfCancelled = false;
        mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, this, null);

Я получаю исключение в этой строке:

mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, this, null);

person Tushar Purohit    schedule 24.06.2016    source источник


Ответы (2)


@AlexKlyubin прав, вам не нужно использовать менеджер отпечатков пальцев для шифрования, только для расшифровки. Чтобы зашифровать текст, все, что вам нужно сделать, это вызвать метод encrypt(String password) выше.

Для расшифровки вы должны использовать FingerprintManagerCompat вместо FingerprintManager. Чтобы прослушивать события отпечатков пальцев и расшифровывать пароль, вам необходимо расширить файл FingerprintManagerCompat.AuthenticationCallback. Я расширил этот класс и реализовал интерфейс обратного вызова:

public class FingerprintAuthentication extends FingerprintManagerCompat.AuthenticationCallback {

    private final Callback mCallback;

    public FingerprintCallback(Callback callback) {
        mCallback = callback;
    }

    @Override
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
        mCallback.onAuthenticationSucceeded(result);
    }

    @Override
    public void onAuthenticationHelp(int messageId, CharSequence message) {
        mCallback.onAuthenticationHelp(messageId, message);
    }

    @Override
    public void onAuthenticationError(int messageId, CharSequence message) {
        mCallback.onAuthenticationError(messageId, message);
    }

    @Override
    public void onAuthenticationFailed() {
        mCallback.onAuthenticationFailed();
    }

    public interface Callback {

        void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result);

        void onAuthenticationHelp(int messageId, CharSequence message);

        void onAuthenticationError(int messageId, CharSequence message);

        void onAuthenticationFailed();
    }
}

При этом вы можете реализовать интерфейс Callback в своем Fragment или Activity, а затем начать слушать события:

private void startListening(boolean cipher) {
    Timber.v("Start listening for fingerprint input");
    mCancellationSignal = new CancellationSignal();
    if(cipher) {
        mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher),
                0, mCancellationSignal, new FingerprintAuthentication(this), null);
    } else {
        setStage(Stage.CREDENTIALS);
    }
}

Наконец, только после успешной аутентификации по отпечатку пальца вы можете расшифровать пароль:

@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
    try {
        mPassword = decryptPassword(result.getCryptoObject().getCipher());
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        exception.printStackTrace();
    }
}

По сути, когда пользователь впервые входит в систему, вы хотите показать ему возможность «использовать отпечаток пальца в будущем»:

введите здесь описание изображения

Если пользователь выбирает эту опцию и нажимает «Войти», это происходит, когда вы вызываете encrypt(). Затем, в следующий раз, когда пользователю потребуется войти в систему, вместо этого вы представляете диалоговое окно отпечатка пальца:

введите здесь описание изображения

Это когда вы звоните startListening(initializeCipher(Cipher.DECRYPT_MODE)).

person Bryan    schedule 24.06.2016
comment
Я шифрую пароль перед вызовом start listning и расшифровываю его в обратном вызове onAuthenticationSuccess, что именно мне нужно сделать - person Tushar Purohit; 25.06.2016
comment
@TusharPurohit Вам не нужно создавать CryptoObject или делать что-либо с FingerprintManager при шифровании. Все, что вам нужно сделать, это убедиться, что пара ключей инициализирована, и инициализировать Cipher в ENCRYPT_MODE, а затем вызвать encrypt в вашей строке. Это позволит вам избежать IllegalArgumentException, остальной код, который вы добавили, не нужен. - person Bryan; 27.06.2016
comment
Нужно ли нам помнить зашифрованный пароль в общих настройках во время активации? Иначе как мы можем создать шифр при расшифровке. - person Bulu; 25.01.2017

В режиме шифрования вы инициализируете экземпляр Cipher с помощью открытого ключа (unrestricted), который не является ключом хранилища ключей Android. Чтобы решить эту проблему, вы можете инициализировать его с помощью открытого ключа Android Keystore (т. е. mKeyStore.getCertificate(KEY_NAME).getPublicKey() вместо unrestricted).

Однако неясно, что вы получаете, закрывая шифрование с открытым ключом при авторизации пользователя. Любой может выполнить операцию шифрования без разрешения пользователя, поскольку для шифрования используется открытый ключ, который по определению не является секретным. В асимметричном шифровании единственными операциями, в которых используются закрытые ключи (которые по определению не являются общеизвестными), являются расшифровка и подпись. Таким образом, обычно имеет смысл использовать дешифрование или вход только при авторизации пользователя.

person Alex Klyubin    schedule 24.06.2016
comment
Я автор исходного кода, упомянутого в вопросе. Причина использования пароля unrestricted связана с ошибкой в ​​Android 6.0, из-за которой открытый ключ блокируется при аутентификации по отпечатку пальца. Только закрытый ключ должен быть заблокирован таким образом, использование копии открытого ключа является обходным путем для этой ошибки. - person Bryan; 24.06.2016
comment
Основная проблема здесь заключается в том, что блокировка криптографических операций, использующих открытый ключ (в отличие от закрытого или секретного ключа) при аутентификации по отпечатку пальца, скорее всего, неверна. Открытый ключ не является секретным, и поэтому любой может выполнить ту же операцию без разрешения пользователя. - person Alex Klyubin; 25.06.2016
comment
@Bryan, так что мне нужен неограниченный ключ от вызова инициализации при шифровании пароля, можете ли вы указать битовый подробный код для потока шифрования и дешифрования - person Tushar Purohit; 25.06.2016
comment
@AlexKlyubin Вы правы, было бы неправильно выполнять публичную операцию при аутентификации по отпечатку пальца. Я обращаюсь к этому в своем ответе. - person Bryan; 27.06.2016
comment
Тогда каков правильный рабочий процесс, если мы требуем, чтобы пользователи аутентифицировались с помощью отпечатков пальцев, чтобы иметь возможность включить аутентификацию по отпечаткам пальцев внутри нашего приложения? Это требование, которого мы не можем избежать. Что я сделал, так это использовал открытый ключ с FingerManager, а затем, как только это было сделано, я зашифровал пароль пользователя с помощью этого открытого ключа. Но теперь на Android 8 выдает эту ошибку. - person Jonas; 04.10.2017