14

I am trying to encrypt and decrypt Strings in my Android application but I keep getting an InvalidKeyException error.

Here is my code:

//Generate Keys method

public void generateKeys() {
    Calendar cal = Calendar.getInstance();
    Date now = cal.getTime();
    cal.add(Calendar.YEAR, 25);
    Date end = cal.getTime();

    KeyPairGenerator kpg = null;
    try {
        kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    }
    try {
        kpg.initialize(new KeyPairGeneratorSpec.Builder(context)
                .setAlias(KEY_ALIAS)
                .setStartDate(now)
                .setEndDate(end)
                .setSerialNumber(BigInteger.valueOf(1))
                .setSubject(new X500Principal("CN=" + KEY_ALIAS))
                .build());
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }

    KeyPair kp = kpg.generateKeyPair();

    KeyStore ks = null;
    try {
        ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);
        Enumeration<String> aliases = ks.aliases();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    KeyStore.Entry entry = null;
    try {
        entry = ks.getEntry(KEY_ALIAS, null);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnrecoverableEntryException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }
    if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
        Log.e(LOG_TAG, "Not an instance of PrivateKeyEntry.");
    }
    else{
        privKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
        pubKey = ((KeyStore.PrivateKeyEntry) entry).getCertificate().getPublicKey();
    }

}

//Encrypt Method

private String encryptString(String value){
    byte[] encodedBytes = null;
    try {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
        cipher.init(Cipher.ENCRYPT_MODE,  pubKey);
        encodedBytes = cipher.doFinal(value.getBytes());
    } catch (Exception e) {
        e.printStackTrace();
    }

    return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
}

//Decrypt Method

private String decryptString(String value){
    byte[] decodedBytes = null;
    try {
        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
        c.init(Cipher.DECRYPT_MODE,  privKey);
        decodedBytes = c.doFinal(Base64.decode(value, Base64.DEFAULT));
    } catch (Exception e) {
        e.printStackTrace();
        Log.e("Error", "Error = " + e);
        return "SECURE_FAILURE";
    }

    return new String(decodedBytes);
}

//Test Code

    generateKeys();
    String encrypted = encryptString("Hello World");
    Log.e("Encrypt", "encrypted = " + encrypted);
    String decrypted = decryptString(encrypted);
    Log.e("Decrypt", "decrypted = " + decrypted);

It looks like the encryption works OK as it prints out something like this:

encrypted = SbA2iWWKQbDL7NTA9xvtjD/viYDdpx9fLRYTSZ8UQzdBy3QLqzkswBY21ErH7FPza3vZys4E4PZw uxaGkRz0aC0FLqsYlbpcJernGm5+D5lRcBOaZmgkNY9pMf0YP75cBbcJdcmb1rDaH40nCRDnEoXv rGESJRqT6p0NMzlZqdd9KO3tqfExwgservAWxPNtRDBbMgE4I/09418jM5Ock5eayfOuv/STwEy6 Ecd56UjFH63h+gP6ed2aMDhBVeExMxvdloY+VnsAxS5Dkoc2GdaljtjRuPK48HQASoJK8EwAMNpz

But when I try to decrypt I get the following error:

java.security.InvalidKeyException: Need RSA private or public key

I can't figure why I am getting this exception? Can anyone help?

Donal Rafferty
  • 19,707
  • 39
  • 114
  • 191
  • Any luck? I'm getting the same exception in Android Marshmallow. – Luis Apr 05 '16 at 17:09
  • I fixed it by using a different provider... see my answer below. – Luis Apr 05 '16 at 18:36
  • 1
    Unless there is some reason for needing asymmetric encryption (RSA) public/private keys just use symmetric encryption such as AES. Symmetric encryption is the general choice for data encryption. Asymmetric encryption data length is limited to the key size and is really slow and no more secure. – zaph Apr 05 '16 at 21:09

3 Answers3

17

this worked for me:

private Cipher getCipher() {
    try {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // below android m
            return Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL"); // error in android 6: InvalidKeyException: Need RSA private or public key
        }
        else { // android m and above
            return Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround"); // error in android 5: NoSuchProviderException: Provider not available: AndroidKeyStoreBCWorkaround
        }
    } catch(Exception exception) {
        throw new RuntimeException("getCipher: Failed to get an instance of Cipher", exception);
    }
}
Kevin Kopf
  • 13,327
  • 14
  • 49
  • 66
lanna blue
  • 181
  • 1
  • 7
  • 4
    Hm... and what if we had created a public and a private RSA keys on, say, on Lollipop and then user upgraded his Android OS to Marshmallow. And our method will return different provider - will it work with previously generated RSA keys? – Kirill Karmazin Sep 29 '18 at 16:07
  • @KirillKarmazin I think if you use the same algorithm, cipher and padding, the result should always be the same, no matter what provider you use. – Nikolay Dimitrov Nov 27 '19 at 01:23
  • I also think that we can just skip the second parameter and let the provider be chosen for us – Nikolay Dimitrov Nov 27 '19 at 01:47
10

Try using a different provider, like this:

Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
Luis
  • 3,451
  • 1
  • 27
  • 41
5

Removing the provider worked for me:

Cipher.getInstance("RSA/ECB/PKCS1Padding")

From Java docs: "This method traverses the list of registered security Providers, starting with the most preferred Provider. A new Cipher object encapsulating the CipherSpi implementation from the first Provider that supports the specified algorithm is returned."

chararray
  • 79
  • 1
  • 12