问题
I am using Android FingerPrintManager
API and creating key pair using KeyPairGenerator, i want to encrypt a password with public key and then decrypt when user is authenticated by entring fingerPrint but as soon i run my project it gets crash and gives
Caused by: java.lang.IllegalArgumentException: Crypto primitive not backed by AndroidKeyStore provider
i hved used code from here : Android Fingerprint API Encryption and Decryption this post says that he is able to do ecryption and decryption an di have 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);
}
}
and from here i am 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 am getting exception at this line :
mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, this, null);
回答1:
@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))
.
回答2:
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.
来源:https://stackoverflow.com/questions/38016968/initializing-fingerpringmanager-crypto-object-getting-crypto-primitive-not-back