7

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)
Christof Buechi
  • 163
  • 1
  • 12
  • Could you show your call to `KeyPairGenerator.getInstance`? – Michael Apr 21 '16 at 10:26
  • Your example says that you initialize the Cipher using getPrivateKey but it doesn't provide the code of getPrivateKey. Please provide the code for getPrivateKey. – Alex Klyubin Apr 21 '16 at 18:33
  • Thank you Michael and Alex - I think I added the necessary details. please let me know if more details are needed and if I should upload the class into github or gist. – Christof Buechi Apr 22 '16 at 06:51
  • Could you find a solution for this problem? – atasoyh Jun 07 '16 at 09:01
  • I was able to get this working with Android M and RSA and I don't think you need to use KeyFactory. Have a look at this code and tweak it to use RSA https://blog.stylingandroid.com/user-authentication-part-1/ – Android Noob Jul 13 '16 at 17:44

2 Answers2

6

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.

Andranik
  • 2,729
  • 1
  • 29
  • 45
1

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);
Karedem
  • 21
  • 2