I'm trying to implement android Fingerprint into a sample application. The used cipher is not recogniced as valid - but I dont know why, since based on the android docs, it should be supported.
The cipher is built on:
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/" +KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
This cipher listed in the official docs.
The keyGenerator and keyFactory which is used later on is generated as follows.
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null); // Ensure the key store can be loaded before continuing.
keyGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyFactory = KeyFactory.getInstance("RSA");
createCipher(); // If this doesn't throw, the cipher we need is available.
I also initialize the keygenerator with that cipher:
keyGenerator.initialize(new KeyGenParameterSpec.Builder(keyAlias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) //
.setBlockModes(KeyProperties.BLOCK_MODE_ECB) //
.setUserAuthenticationRequired(true) //
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) //
.build());
keyGenerator.generateKeyPair();
I also add the public key to the encryption process, while the public key is generated this way:
private PublicKey getPublicKey() throws GeneralSecurityException {
PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
KeySpec spec = new X509EncodedKeySpec(publicKey.getEncoded());
return keyFactory.generatePublic(spec);
}
edit: added the part of the private key:
PrivateKey getPrivateKey() throws GeneralSecurityException {
return (PrivateKey) keyStore.getKey(keyAlias, null);
}
the actual fingerprint handling is then as follows:
Cipher cipher = createCipher();
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), cancellationSignal,
0, new FingerprintManager.AuthenticationCallback() {/* cutted */ }, null);
the decryption:
cipher = createCipher();
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {}, null);
Resulting in the following:
Process: com.example.android.fingerprintdialog, PID: 16254 java.lang.IllegalArgumentException: Crypto primitive not backed by AndroidKeyStore provider: javax.crypto.Cipher@2419dda, spi: com.android.org.conscrypt.OpenSSLCipherRSA$PKCS1@4a4d20b
full stacktrace:
04-21 11:48:00.031 16254-16254/com.example.android.fingerprintdialog E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.android.fingerprintdialog, PID: 16254
java.lang.IllegalArgumentException: Crypto primitive not backed by AndroidKeyStore provider: javax.crypto.Cipher@2419dda, spi: com.android.org.conscrypt.OpenSSLCipherRSA$PKCS1@4a4d20b
at android.security.keystore.AndroidKeyStoreProvider.getKeyStoreOperationHandle(AndroidKeyStoreProvider.java:160)
at android.hardware.fingerprint.FingerprintManager$CryptoObject.getOpId(FingerprintManager.java:248)
at android.hardware.fingerprint.FingerprintManager.authenticate(FingerprintManager.java:468)
at android.hardware.fingerprint.FingerprintManager.authenticate(FingerprintManager.java:429)
at com.example.android.fingerprintdialog.MainActivity.tryToEncrypt(MainActivity.java:212)
at com.example.android.fingerprintdialog.MainActivity.access$000(MainActivity.java:61)
I also had the same problem now, with new androidx.biometric
. I was getting the same exact error while trying to perform biometric auth for encryption e.g.:
val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/"
+ KeyProperties.BLOCK_MODE_ECB + "/"
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA), keyStore))
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
The getPublicKey
method and all the other parameters are equivalent to the ones that author listed.
Then I relized that we are doing this wrong.
All the examples I could find in this topic use Symetric Cryptographi with AES keys. For this type of cryptography the key is one and only for encryption and decryption, thus it needs to be protected with Biometric authentication no metter if we are doing encryption or decryption. That's why in all the examples we see this code for encryption prompt:
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
But with RSA (aka. Asymetric) encryption thigs are different. The encryption key and decryption key are different. The encryption key is the private key, thus it does not need to be protected in any way. Only the decryption, private key needs to.
That's why we are getting the encryption as we are trying to open the biometric auth prompt to activate the public key which is noncence as the public key is not a secret.
The solution is very simple. Just call the authenticate
method without the CryptoObject
(biometricPrompt.authenticate(promptInfo)
) and later, when the authentication is successful, use your publik key to do the encryption.
Hope this can help to someone eles as I could not find any information related to this topic and only after hours of thinking I got what's going wrong there.
i meet the same exception, i fixed it when i Specify the Provider of Cipher and others; for example:
String alg = "AES";
Cipher cipher = Cipher.getInstance(alg, "SunJCE");
KeyGenerator generator = KeyGenerator.getInstance(alg, "SunJCE");
SecretKey key = generator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With