3

I would like to encrypt a String with RSA encryption. My public/private keys were generated and stored in DB. In android, I use this code:

public static String encryptRSAToString(String text, String strPublicKey) {
    byte[] cipherText = null;
    String strEncryInfoData="";
    try {

    KeyFactory keyFac = KeyFactory.getInstance("RSA");
    KeySpec keySpec = new X509EncodedKeySpec(Base64.decode(strPublicKey.trim().getBytes(), Base64.DEFAULT));
    Key publicKey = keyFac.generatePublic(keySpec);

    // get an RSA cipher object and print the provider
    final Cipher cipher = Cipher.getInstance("RSA");
    // encrypt the plain text using the public key
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    cipherText = cipher.doFinal(text.getBytes());
    strEncryInfoData = new String(Base64.encode(cipherText,Base64.DEFAULT));

    } catch (Exception e) {
    e.printStackTrace();
    }
    return strEncryInfoData.replaceAll("(\\r|\\n)", "");
}

For debug purpose, I try to call 2 times this method with the same parameters and String result were similar (as expected).

I want to generate the same encrypted String in java. However, "android.util.Base64" class is not available in Java, so I've tried with the default Base64 class:

public static String encryptRSAToString(String text, String strPublicKey) {

        byte[] cipherText = null;
        String strEncryInfoData="";
        try {

        KeyFactory keyFac = KeyFactory.getInstance("RSA");
        KeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(strPublicKey.trim().getBytes()));
        Key publicKey = keyFac.generatePublic(keySpec);

        // get an RSA cipher object and print the provider
        final Cipher cipher = Cipher.getInstance("RSA");
        // encrypt the plain text using the public key
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        cipherText = cipher.doFinal(text.getBytes());
        strEncryInfoData = new String(Base64.encodeBase64(cipherText)); 

        } catch (Exception e) {
        e.printStackTrace();
        }
        return strEncryInfoData.replaceAll("(\\r|\\n)", "");
    }

But the String generated in Android and the one in java are different.

Generated in Android side :

Ky2T4j1JdI081ZESVJgxZXEf/xmtpehfv/EwpVvKQxUu1JI8lwXP2Rc66jHZRc0P846ZYuF3C9YEmWoKbXGXk2MBuT5KVxa2yoTbwZlMmhVOX3X3Efq0VyaO5zZ4qavIq036cA3MzvQbUAb678UdbALW/CjRCsOdeH+hSCzNQ+0=

Generated in JAVA side :

XhSLxfiJUUdZW5kWh0MEPSrqoROBBhNC/krfTx+sdnXML3WegYbMzSvNnPgB8+8Z9joEUBMmoeBI1OhTF6qPFL1EEixkFYAkGaryEFxvN/aFI75kEUj71OHNzAHAuvS+h+9Nssx9psSZ5gc2OoLQH0QtbGDyXB4p+qUGFCde4tY=

Does someone know how to solve my issue ?

thank you

4
  • The only difference is the Base64 class. For the java side, I use the org.apache.commons.codec.binary.Base64
    – johann
    Commented Jun 17, 2015 at 11:01
  • You do not specify the charset when calling getBytes(). The default charset differs on Android and Java/Windows.
    – Robert
    Commented Jun 17, 2015 at 12:08
  • There is a great of sloppiness (e.g. Cipher.getInstance("RSA");) in this code but the bottom line is: why do you believe the string should be the same? RSA encryption if done correctly incorporates a random component so the outputs, even for identical input, will be the different. Commented Jun 18, 2015 at 1:12
  • If I call encryptRSAToString 2 times with the same parameters in Android, the output string will be the same.
    – johann
    Commented Jun 18, 2015 at 3:33

3 Answers 3

8

It looks like you've been undone by relying on defaults. Never do that if you hope for interoperability.

Here are the two examples of mistakenly relying on defaults in your code that I've found.

final Cipher cipher = Cipher.getInstance("RSA");

The tranformation string is supposed to be of the form "algorithm/mode/padding" but you've left off the mode and padding specifications. As a result you got default values for those. The defaults are evidently different on Android and Oracle Java. You should always fully specify the transformation, for example:
final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");

Another bad example is cipherText = cipher.doFinal(text.getBytes());

In text.getBytes() you are relying on the no-args getBytes() method which uses the default charset for the platform. But this default charset differs on different platforms, and thus this is not portable. In almost all cases I've run across you should specify the UTF-8 charset. So the correct line would thus be
cipherText = cipher.doFinal(text.getBytes("UTF-8"));

and the correct string constructor to use to recreate the original string in the decrypt method is the String(byte [] data, String charsetName).

1
  • Thank you for your precious help !
    – johann
    Commented Jun 19, 2015 at 2:23
2

I can´t comment yet so I answer.

It is possible that different default configurations are being used. Check this question: Is there any difference between Apache's Base64.encodeBase64 and Android's Base64.encode with Base64.Default flag?

0

There are deviations of different cipher and hash implementations. I would suggest using OpenSSL as a common implementation.

5
  • My android application is already published and it s not possible to change the android code.
    – johann
    Commented Jun 17, 2015 at 12:21
  • The essential reason of your problem is even if Android run on top of JVM, the JVM is modified, and some underlying libraries have deviations(compared with cost-off-the-shelf JVM)
    – frogcdcn
    Commented Jun 17, 2015 at 14:17
  • This makes no sense since openssl is not a Java library. Commented Jun 18, 2015 at 1:14
  • @JamesKPolk Java implements its own crypto algorithms independent of openssl. That accounts for the deviation.
    – frogcdcn
    Commented Jun 18, 2015 at 1:33
  • No, that is not what is happening here. Commented Jun 18, 2015 at 10:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.