Android - Encode & Decode RSA with Private Key?
Asked Answered
J

2

6

I am trying to encode and decode Strings on Android using a Private Key generated and stored using the Android Key Store Provider that was introduced in Android 4.3

I can successfully generate and get the private key using the following code:

 private void generatePrivateKey(Activity context, String alias){
    /** Generate a new entry in the KeyStore by using the  * KeyPairGenerator API. We have to specify the attributes for a  * self-signed X.509 certificate here so the KeyStore can attach  * the public key part to it. It can be replaced later with a  * certificate signed by a Certificate Authority (CA) if needed.  */

    Calendar cal = Calendar.getInstance();
    Date now = cal.getTime();
    cal.add(Calendar.YEAR, 1);
    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(alias)
                .setStartDate(now)
                .setEndDate(end)
                .setSerialNumber(BigInteger.valueOf(1))
                .setSubject(new X500Principal("CN=" + alias))
                .build());
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }

    KeyPair kp = kpg.generateKeyPair();

    /*
 * Load the Android KeyStore instance using the the
 * "AndroidKeyStore" provider to list out what entries are
 * currently stored.
 */
    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();
    }

    /*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */

    KeyStore.Entry entry = null;
    try {
        entry = ks.getEntry(alias, null);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnrecoverableEntryException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }
    if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
        Log.w("E", "Not an instance of a PrivateKeyEntry");
    }
    else{
        Log.w("E", "Got Key!");
        privateKeyEntry = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
    }

}

And here is the code I am using for encrypt (encode) and decrypt (decode):

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

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

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

    return new String(decodedBytes);
}

The Encryption appears to work fine but when I try to decrypt it I get the following error:

javax.crypto.BadPaddingException: error:0407106B:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02

Googling this seems to suggest that the private key used for decryption is different to the one used for decryption but in my code I use the exact same private key for both. I have also seen it suggested to set the key size manually but doing this in the KeyPairGenerator builder like this:

.setKeySize(1024);

Did not work and seems to be only available on API 19, I need to target API 18.

Can anyone help point me in the right direction as to a solution?

Judoka answered 25/9, 2014 at 17:33 Comment(0)
M
6

You are not using the public key for encryption.

When you are using asymmetric encryption algorithms, you need to use the public key to encrypt your data, and the private key only to decrypt it again.

Besides encryption, you can also use the private key for signing, but that's not what you want here, so let's forget about that for the moment.

If you take the public key from the generated pair, when you encrypt your string, and the private key when decrypting, you should get the desired result. The public key you can extract by accessing the certificate from the keystore-object that holds your private key.

Alternatively you could also use a symmetric algorithm like AES and by that make your work a lot easier. Plus, symmetric algorithms are usually much faster, which is why asymmetric algorithms are never used purely, but in conjunction with symmetric algorithms, building so-called hybrid algorithms.

Macdonell answered 25/9, 2014 at 20:0 Comment(6)
Perfect Mjoellnir that works, I was unaware you could also get the public key from the KeyPairGenerator and thought that the single key returned from it might take care of both. I am also interested to learn more about AES, do you know if it is possible to use this with the Android Key Store Provider? My reading on it suggests that AES isn't support and all examples seem to use RSA. I previously implemented a working solution using AES but was unable to find a way to securely store my private key so I turned to the Android Key Store Provider and RSA.Judoka
Well, I am using AES in several Android applications with no problems at all, and besides that it has become quite an integral part in the usual daily SSL/TLS encrypted communication on the internet at large, so not supporting it on a platform like Android really is no option. However, I have not worked with the KeyStore-object so often before, so I can't really tell you so much about it's specific quirks. But if you use a string like this "AES/CBC/PKCS5Padding" for your Cipher-instance, and "AES" for the algorithm where needed, it should do nicely.Macdonell
Do you require storing your private key on the device in any of your Android applications? If so how do you securely do that and still have the ability to use AES?Judoka
It depends. Mostly, I use AES for file encryption. I have the user enter a password, and use that, a salt and an IV to generate an AES key. I don't actually store the key, only the salt and the IV (in the file), and the password has always to be entered manually by the user. Upon decryption, I can reconstruct the key from the password (entered by the user again), the salt and IV read from the file (utterly useless without the password, so safe to store in the file), and then I can use the key for decryption.Macdonell
I see, in my application I am trying to avoid asking the user to retype passwords every time, I guess it is a question of how secure the data needs to be? Can I ask if you have any good references for starting out with understanding the AES encryption you have implemented on Android?Judoka
This actually leads a bit off of your original question, which should be avoided. But in short: I am using Android's on-board tools, like Cipher, CipherInputStream, CipherOutputStream, etc. If you look in that direction, I am sure you will find your answers.Macdonell
D
1

Signature generation is not the same thing as encryption. You need to encrypt with the public key and decrypt with the private key if you want encryption. If you want signature generation, you need to sign with the private key and verify with the public key. This order cannot be reversed nor can it be mixed (securely).

Disarrange answered 25/9, 2014 at 18:38 Comment(4)
Thanks owlstead, my aim is to encrypt a password, save it to the sharedpreferences and then decrypt it as I take it out of the sharedpreferences. I can successfully do this with a SecrectKey but I was hoping the approach I was taking above would allow me to securely store a key and use this to encrypt and decrypt. Is this simply not possible with the approach I have taken?Judoka
I guess this is answered now you got hold of the public key? Note that usually encryption of passwords should only be performed for saving them for other services. To store them safely in a database, use PBKDF2 or bcrypt instead.Disarrange
Hi owlstead, yes this is working now but I am also interested in furthering my knowledge in this area. Can you clarify what you mean by other services? My reason for doing this is to store users login details on the device so they don't have to go through a tedious login screen each time. Do you have any good references for starting out with PBKDF2 or bcrypt with a particular focus towards Android?Judoka
Hmm, no for that you need encryption. PBKDF2 or bcrypt is more server side (sorry, forgot the Android tag here), although you could possibly offload some of the CPU power required to perform PBKDF2 to the android device.Disarrange

© 2022 - 2024 — McMap. All rights reserved.