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

johann
  • 1,115
  • 8
  • 34
  • 60
  • The only difference is the Base64 class. For the java side, I use the org.apache.commons.codec.binary.Base64 – johann Jun 17 '15 at 11:01
  • You do not specify the charset when calling `getBytes()`. The default charset differs on Android and Java/Windows. – Robert Jun 17 '15 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. – President James K. Polk Jun 18 '15 at 01:12
  • If I call encryptRSAToString 2 times with the same parameters in Android, the output string will be the same. – johann Jun 18 '15 at 03:33

3 Answers3

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).

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
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?

Community
  • 1
  • 1
javi_swift
  • 377
  • 6
  • 16
0

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

frogcdcn
  • 391
  • 1
  • 4
  • 14