Initializing FingerpringManager.Crypto Object, getting Crypto primitive not backed by AndroidKeyStore provider?

℡╲_俬逩灬. 提交于 2019-12-05 21:15:23

@AlexKlyubin is right, you do not need to use the fingerprint manager for encryption, only decryption. In order to encrypt the text, all you need to do is call the encrypt(String password) method above.

For decryption, you should be using FingerprintManagerCompat instead of FingerprintManager. In order to listen for fingerprint events and decrypt the password, you need to extend the FingerprintManagerCompat.AuthenticationCallback. I extended this class, and implemented a callback 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();
    }
}

With this you can implement the Callback interface in your Fragment or Activity, then 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);
    }
}

Lastly, only after the fingerprint authentication has succeeded can you decrypt the password:

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

Basically, when the user first signs in, you want to show an option for them to "use fingerprint in the future":

If the user selects this option and clicks login, this is when you call encrypt(). Then, the next time the user is required to login, you present the fingerprint dialog instead:

This is when you call startListening(initializeCipher(Cipher.DECRYPT_MODE)).

In encryption mode, you're initializing the Cipher instance using a public key (unrestricted) which is not an Android Keystore key. To resolve the issue, you could initialize it with Android Keystore public key (i.e., mKeyStore.getCertificate(KEY_NAME).getPublicKey() instead of unrestricted).

However, it's not clear what you gain by gating public key encryption on user authorization. Anybody can perform the encryption operation, without the user authorizing that, because encryption uses a public key which by definition is not secret. In asymmetric crypto, the only operations that involve private keys (which are not publicly known by definition) are decryption and signing. Thus, it usually only makes sense to gate decryption or signing on user authorization.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!