45

I'm following this tutorial: How to use the Android Keystore to store passwords and other sensitive information. It (loosely) ties up with the Google Sample app: BasicAndroidKeyStore.

I can encrypt my data using the public key, and I can decrypt on devices running Lollipop. However I have a Nexus 6 running marshmallow and this crashes giving the error:

java.lang.RuntimeException: Unable to create application com.android.test: java.lang.ClassCastException: android.security.keystore.AndroidKeyStoreRSAPrivateKey cannot be cast to java.security.interfaces.RSAPrivateKey

Here is the code it crashes on:

KeyStore.Entry entry;

//Get Android KeyStore
ks = KeyStore.getInstance(KeystoreHelper.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);

// Weird artifact of Java API.  If you don't have an InputStream to load, you still need to call "load", or it'll crash.
ks.load(null);

// Load the key pair from the Android Key Store
entry = ks.getEntry(mAlias, null);

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;

//ERROR OCCURS HERE::
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey();

Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");

output.init(Cipher.DECRYPT_MODE, rsaPrivateKey);

I'm reluctant to put this down to an Android M oddity as I see no reason why the java crypto libraries would have changed. If the M release comes around and our app immediately crashes on M I'll be in big trouble.

I am doing something wrong? The error very specifically says you can't cast to RSAPrivateKey, so does anyone know a better way to get the RSAPrivateKey from the entry?

Many many thanks.

jww
  • 97,681
  • 90
  • 411
  • 885
James
  • 3,485
  • 3
  • 20
  • 43

4 Answers4

63

I managed to get this working by removing the Provider from Cipher.getInstance and not casting to a RSAprivateKey.

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;

Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding");
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());

I'm not 100% but I think the reason for this I believe is the change in marshmallow from OpenSSL to BoringSSL. https://developer.android.com/preview/behavior-changes.html#behavior-apache-http-client

Anyway, the above worked for M and below.

James
  • 3,485
  • 3
  • 20
  • 43
  • 3
    Now open on AOSP: [Issue 205450: Crash casting AndroidKeyStoreRSAPrivateKey to RSAPrivateKey](https://code.google.com/p/android/issues/detail?id=205450). – jww Mar 30 '16 at 18:49
  • 2
    this solution works on Lollipop. But on marshmallow it throws: java.security.InvalidKeyException: Need RSA private or public key – deviant Aug 05 '16 at 18:30
  • 4
    @resp78 On Android 6.0 you should not use "AndroidOpenSSL" for cipher creation, it would fail with "Need RSA private or public key" at cipher init for decryption. Simply use Cipher.getInstance("RSA/ECB/PKCS1Padding") and it will work. – Sid Aug 10 '16 at 20:34
  • Works on marshmallow and Android N preview 5. Thanks. – Misagh Emamverdi Aug 16 '16 at 05:59
  • Your solution is not enough to prevent it crash on Andr 6. I tried it and do more a little things is set encryptPadding: setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) replace for ENCRYPTION_PADDING_NONE and then it worked. – Khang Tran Dec 15 '16 at 04:44
  • Thanks!! it worked for me on Nougat. I was using "AndroidOpenSSL" on Cipher getInstance. – Hermandroid Feb 09 '17 at 16:58
  • The solution worked for me. On Android 6.0+, privateKeyEntry.getPrivateKey() will return AndroidKeyStoreRSAPrivateKey object instead of RSAPrivateKey object with Cipher initialized with "RSA/ECB/PKCS1Padding". If you specify "AndroidOpenSSL" as provider for decryption, the old OpenSSLCipherRSA class will used to handle the private but it does not support AndroidKeyStoreRSAPrivateKey. Therefore, the exception occurs. If you use the default provider for decryption, AndroidKeyStoreRSACipherSpi.PKCS1Padding will be used and it supports AndroidKeyStoreRSAPrivateKey. – Keeeeeenw May 03 '17 at 23:13
  • Note that I had this problem on both a Nexus/Marshmallow device and a Sony/Oreo device (but not several others). Removing the cast fixed both devices. – Andy Krouwel Jan 02 '18 at 13:22
  • The AndroidKeyStoreRSAPrivateKey class is incorrectly written and should be derived from RSAPrivateKey in order to be compatible with secure padding. This is a bug in Android - not your code. You can fix it by creating a wrapper class derived from RSAPrivateKey and forwarding methods to it. – Erik Aronesty Jan 03 '18 at 14:07
14

Issue

  1. We are trying to parse "java.security.PrivateKey to java.security.interfaces.RSAPrivateKey" & "java.security.PublicKey to java.security.interfaces.RSAPublicKey". That's why we are getting ClassCastException.

Solution

  1. We don't need to parse the key, we can directly use the "java.security.PrivateKey" & "java.security.PublicKey" for Encryption & Decryption.

Encryption

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)entry; 
PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey(); // Don't TypeCast to RSAPublicKey

Decryption

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)entry;
PrivateKey privateKey = privateKeyEntry.getPrivateKey(); // Don't TypeCast to RSAPrivateKey
Vasanth
  • 6,257
  • 4
  • 26
  • 19
5

I resolved this issue by also following this(apart from @James answer above): On Android 6.0 you should not use "AndroidOpenSSL" for cipher creation, it would fail with "Need RSA private or public key" at cipher init for decryption. Simply use Cipher.getInstance("RSA/ECB/PKCS1Padding") and it will work.

Sid
  • 1,270
  • 2
  • 15
  • 30
-4

I havn't tried it but you should be able to cast the android.security.keystore.AndroidKeyStoreRSAPrivateKey to the following separately. These should be the interfaces you need:

  1. java.security.PrivateKey
  2. java.security.interfaces.RSAKey
James
  • 88
  • 7