Java – Initialize the FingerpringManager.Crypto object to get crypto primitives that are not supported by the AndroidKeyStore provider?

Initialize the FingerpringManager.Crypto object to get crypto primitives that are not supported by the AndroidKeyStore provider?… here is a solution to the problem.

Initialize the FingerpringManager.Crypto object to get crypto primitives that are not supported by the AndroidKeyStore provider?

I’m using the Android FingerPrintManager API and creating a key pair using KeyPairGenerator, I want to encrypt the password with the public key and then decrypt it when the user authenticates by typing fingerPrint, but once I run my project it crashes and gives

Caused by: java.lang.IllegalArgumentException: Crypto primitive not
backed by AndroidKeyStore provider

I used the code here: Android Fingerprint API Encryption and Decryption
The article says that he was able to encrypt and decrypt, and di followed the same code and steps.
Here is my code

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

I start initializing my 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);

I encountered an exception on this line :

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

Solution

@AlexKlyubin Yes, you don’t need to use a fingerprint manager for encryption, just decrypt it. In order to encrypt text, all you need to do is call the encrypt(String password) method above.

For decryption, you should use FingerprintManagerCompat instead of FingerprintManager. In order to listen for fingerprint events and decrypt passwords, you need to extend FingerprintManagerCompat.AuthenticationCallback. I extended this class and implemented a callback interface (interface):

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

This way you can implement the callback interface in your fragment or activity and start listening for events

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

Finally, the password can only be decrypted after the fingerprint authentication is successful:

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

Basically, when a user logs in for the first time, you want to show them an option to “use fingerprint in the future”:

enter image description here

If the user selects this option and clicks Sign In, that’s when you call encrypt(). Then, the next time the user is asked to sign in, you’ll display the fingerprint dialog box:

enter image description here

This is called startListening(initializeCipher(Cipher.DECRYPT_MODE)).

Related Problems and Solutions