Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Fingerprint - Crypto primitive not backed by AndroidKeyStore provider

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)
like image 319
Christof Buechi Avatar asked Oct 31 '22 05:10

Christof Buechi


2 Answers

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.

like image 152
Andranik Avatar answered Nov 11 '22 09:11

Andranik


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);
like image 39
Karedem Avatar answered Nov 11 '22 11:11

Karedem