RSA Encryption disparity between Android and Java

2019-03-30 07:42发布

问题:

Firstly, apologies for the amount of code I'm about to post. I'm trying to use the RSA public key from my Java application to encrypt a message in an Android app, and then send the ciphertext back to a Java environment for decryption, but upon attempting to decrypt I always get this error:

javax.crypto.BadPaddingException: Decryption error
    at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380)
    at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:291)
    at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
    at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:382)
    at javax.crypto.Cipher.doFinal(Cipher.java:2087)
    ...

The ciphertext does contain the correct number of bytes (512), so it's confusing to see a "bad padding" exception. Other similar posts on SO have suggested using "RSA/ECB/PKCS1Padding" as the algorithm, but this does not work.

Annoyingly, encryption and decryption in the Android environment (using Base64.URL_SAFE as the 'base64Type') works just fine, I just can't seem to get the initial encryption working with the public key produced via Java.

I have extracted the bare minimum code into examples, as shown below:

Android Code

private void exampleMethod(){
    String messageString = "Why does this not work in Android?";

    String serverPubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoApIBIna77xq4j+M2RmyIhsB++41NHcY4KIPfX4VP4ADnkO+7ejbs4le/twrPtYGESVPF9czSMB5bzmTBZtq0jC8oT/6wiDIBlSuzo4fBrGociBIuaOjyG/j3ZhpcWpWPXuzER+ehuQ+8hZkMuJdK9IodqPR+5jmCef4rXoKObwS02LYQ1co5dEmtZVQRmmeYaVnWibd/s1d4KKGvSzXap3YBTf8peH5UGIQrLOTqvX0bo34xFxmj5U0H3xudnnwuVAlQlj9KiHPPABuwNtm1buRKJb5HZhSCveyT/2YAOmQqGrVN/nALtlZyTDZNs//Vp1zb9exSuG0t5xFc+pn4QIDAQAB";

    String encryptedMessageString = getUrlEncodedCipherText(messageString, serverPubKey, Base64.NO_WRAP);
    /**
     * CipherText is ALWAYS the same and does not decrypt: DA_-RpCki-mjF6tSwiP2IhuW2UfPzZC7A9oVTTNptjT73HtROiQZvUC0Z2veJ5VwVx4toolvLErQmKKoQlqELSD756bu8ohEQwgJ4Xsu-3tXv-uEi5a9a_u19WnNLIF7tayDUhFeD2RzNvW895y1v-D30TvQRskNCFJfnjaytr_vmcVv8HrXURCmG6AMltaqdN72zh8p6VkKcjXSLiCApH957GXSqJCRzxbaQwf8X5EJfn8CQrPDGbE3gdhc2_hFwXQNIdxPxrOLtVbaFp9i_4GRWXJ6E2jHttV2bDv_uSVIz3OBzh7EkJiCnl3c904sH8QZae8c3SQyrTxVL7EpIA,,
     */
}

public static String getUrlEncodedCipherText(String plainText, String pubKey, int base64Type){
    try {
        final PublicKey publicKey = loadPublicKey(pubKey, base64Type);
        final byte[] cipherBytes = encrypt(plainText, publicKey);
        String cipherText = base64Encode(cipherBytes, base64Type);
        String urlEncodedCipherText = urlEncode(cipherText);
        return urlEncodedCipherText;
    }
    catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

public static final String ALGORITHM = "RSA";

public static PublicKey loadPublicKey(String stored, int base64Type) throws GeneralSecurityException {
    String pubKey = stored.replace(BEGIN_PUBLIC_KEY, "");
    pubKey = pubKey.replace(END_PUBLIC_KEY, "");

    byte[] data = Base64.decode(pubKey, base64Type);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
    KeyFactory fact = KeyFactory.getInstance(ALGORITHM);
    PublicKey pub = fact.generatePublic(spec);
    return pub;
}

public static byte[] encrypt(String text, PublicKey key) {
    byte[] cipherText = null;
    try {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        cipherText = cipher.doFinal(text.getBytes());
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    return cipherText;
}

public static String base64Encode(byte[] cipherBytes, int base64Type){
    byte[] base64Cipher = Base64.encode(cipherBytes, base64Type);
    return new String(base64Cipher);
}

public static String urlEncode(String text){
    return text.replace("+", "-").replace("/", "_").replace("=", ",");
}

Java Code

private void exampleMethod(){
    String pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoApIBIna77xq4j+M2RmyIhsB++41NHcY4KIPfX4VP4ADnkO+7ejbs4le/twrPtYGESVPF9czSMB5bzmTBZtq0jC8oT/6wiDIBlSuzo4fBrGociBIuaOjyG/j3ZhpcWpWPXuzER+ehuQ+8hZkMuJdK9IodqPR+5jmCef4rXoKObwS02LYQ1co5dEmtZVQRmmeYaVnWibd/s1d4KKGvSzXap3YBTf8peH5UGIQrLOTqvX0bo34xFxmj5U0H3xudnnwuVAlQlj9KiHPPABuwNtm1buRKJb5HZhSCveyT/2YAOmQqGrVN/nALtlZyTDZNs//Vp1zb9exSuG0t5xFc+pn4QIDAQAB";
    String privKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCgCkgEidrvvGriP4zZGbIiGwH77jU0dxjgog99fhU/gAOeQ77t6NuziV7+3Cs+1gYRJU8X1zNIwHlvOZMFm2rSMLyhP/rCIMgGVK7Ojh8GsahyIEi5o6PIb+PdmGlxalY9e7MRH56G5D7yFmQy4l0r0ih2o9H7mOYJ5/itego5vBLTYthDVyjl0Sa1lVBGaZ5hpWdaJt3+zV3gooa9LNdqndgFN/yl4flQYhCss5Oq9fRujfjEXGaPlTQffG52efC5UCVCWP0qIc88AG7A22bVu5EolvkdmFIK97JP/ZgA6ZCoatU3+cAu2VnJMNk2z/9WnXNv17FK4bS3nEVz6mfhAgMBAAECggEBAIax4IchV0jqdbLR9cNK4yfdP0A/7jun+SImg48FLPDy1xi+v9UQZMioV3F88FDEZPrNQdI45wrWI94+wMS5V6BsMHYumOgGGxNo9m8WInrJz5GuJkdHuLMbqNZ6TlSMQOUiVUWWLSAuveOWgOJqriwRhsjDfBmbSBESUbP/wNdxX42RJudKJbVUV07urjFc3VAUrX+Sbj9KMZRe10pOzes7WKq9NMyQuitLcwCPzs2pQoXW1LdZYrVSi6MOqE1WHSL6VAgPx3cHYl7yznhspLmvDgnKTwnVbRo3BBwkxNbpvZXzgJBLEPLUtLqwNLKkU0aSF9MT5TABx4tfCaRQIiECgYEA70980I9mK74tjXlFrMKaLGigjHBss+Q/b7cRAtlQAhVOn0FCqz4Fc4iBri0iPekVIZ09lRehacEstTR8JBImMW2mqGyMwBbPaqQOf6xZ0pIoYb0ODAIjUNTWoBEr72+ko5HjaoQbxeb2QGUhMe/t3M1CMsrETEQTdA+qNP5C61UCgYEAqzOL7sSsNfTYbAF156qPPqx1IyXNqu0wbKa/zufCxGlFJDkaYoYIECKdLbpI2fqJsENqpZHgOnT0+LbqhFn07NIe1zT/zf0rh7w5fqxqy3Srs4+Mj6HwTIC7QpeXjiHxQuVrfi2W2ZatjQi8froxtEj3mpYKHsl0Ia89JSczQl0CgYAqHPXdCe8z8XK4u8esIE7bU8o1DK/EdH1JXpDqzG1NAIzmb6iY1ABHlZUknqKw/GyQjshAjXkFUE5a0RKrkloQRriWWQvn3dvAa4B1rVHdQYVDte5b5KBsYBgo8PynVSFG+6xmmTr996gMKv/NduiH+8MThyVGOpCl0v/j9X63RQKBgEmOhir6eXtdTbdqETyOPamR82o8jddIvauRIYxGa5p0GG7t0fZO3BwCo0HIbhCp4orHDIVC3fJ/2dkazjw7Yk52ISYZ8WaRxig1qQZSEjiEUll97ciwrUxRayO7ejRpRP2XEM5PzCaE5OBZxpM0cLKjPy8+E+8SY0Etx7m01ANJAoGBALUubgeKx1fut80YHLDmxOiTg9olFJi83Lj1TPQ0fRCXdJX6pHCSypBScoXuJYVwuIavHhTf8DPQ6OONq/V3DXKsGLydK/2E5yg+bz3qYfYslb3vDkZovNJDmfoyR0XakWbUTotntUQqodLk8Q9klHKp6oy+MkGY57R5OhIZBGPa";

    String message = "Why does this not work in Android?";
    String encryptedMessage = getUrlEncodedCipherText(message, pubKey);

    try {
        byte[] base64Decoded = Base64.decodeBase64(encryptedMessage.getBytes(Charset.forName("UTF-8")));
        String decryptedMessage = decrypt(base64Decoded, loadPrivateKey(privKey));
        System.out.println("decryptedMessage: " + decryptedMessage);
        /**
         * This works! Ciphertext always comes out different, as expected, and decodes successfully.
         */
    }
    catch (Exception e){
        e.printStackTrace();
    }
}

public static String getUrlEncodedCipherText(String plainText, String pubKey){
    try {
        final PublicKey publicKey = loadPublicKey(pubKey);
        final byte[] cipherBytes = encrypt(plainText, publicKey);
        String cipherText = base64Encode(cipherBytes);
        String urlEncodedCipherText = urlEncode(cipherText);
        return urlEncodedCipherText;
    }
    catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

public static final String ALGORITHM = "RSA";

public static PublicKey loadPublicKey(String stored) throws GeneralSecurityException {
    String pubKey = stored.replace(BEGIN_PUBLIC_KEY, "");
    pubKey = pubKey.replace(END_PUBLIC_KEY, "");

    byte[] data = Base64.decodeBase64(pubKey);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
    KeyFactory fact = KeyFactory.getInstance(ALGORITHM);
    PublicKey pub = fact.generatePublic(spec);
    return pub;
}

public static byte[] encrypt(String text, PublicKey key) {
    byte[] cipherText = null;
    try {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        cipherText = cipher.doFinal(text.getBytes());
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    return cipherText;
}

public static String decrypt(byte[] encrypted, PrivateKey key) {
    byte[] decryptedText = null;
    try {
        final Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        decryptedText = cipher.doFinal(encrypted);
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return new String(decryptedText);
}

public static String base64Encode(byte[] cipherBytes){
    byte[] base64Cipher = Base64.encodeBase64(cipherBytes);
    return new String(base64Cipher);
}

public static String urlEncode(String text){
    return text.replace("+", "-").replace("/", "_").replace("=", ",");
}

I'm aware that the problem has to be to do with difference in the way Android and Java interpret the RSA algorithm, and/or differences with the Base64 encode/decode, but I'm stumped. Any assistance greatly appreciated.

回答1:

Solved it! The issue was that I was using the same ALGORITHM String for Cipher, KeyFactory etc. I split this into two different Strings, and the full solution for my problem is...

Android Code

private void exampleMethod(){
    String messageString = "This actually works in Java AND Android!";

    String serverPubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjS7T3WJ+VLucnUP5WYryeg+hhOjZZl5VxwvJAgo4GrXaXdernTPtmXnOSUlbhd928QRCip7D3rLwJNvGIwhPa6coA+YQnj+aHQC02AvCJP/9jpeNmm5MASZfYFXrdmOrMhAPpDZ4rUk1mqtvpwBkYmW3VbMtG336wT1bAIKPHCZuI2n6glupJvs8gK0NuIoAPRlxiQmQD7NCcRx1Et4JmqOMIRC+HqdGv9GGqC/0PB0Fv6LXi8GdzJQPMdoRLR0rvVykNeIzmcimejoIVjI78XUZeB1hF7p55h6W4C4Xm/PrnzKuXw4lBVehZtRhyIvNO62G/eNEZ3tup1/m+vkzHQIDAQAB";

    String encryptedMessageString = getUrlEncodedCipherText(messageString, serverPubKey, Base64.NO_WRAP);
    System.out.println("encryptedMessageString: " + encryptedMessageString);
    /**
     * This works! Ciphertext always comes out different, as expected, and decodes successfully when fed into Java application.
     */
}

public static String getUrlEncodedCipherText(String plainText, String pubKey, int base64Type){
    try {
        final PublicKey publicKey = loadPublicKey(pubKey, base64Type);
        final byte[] cipherBytes = encrypt(plainText, publicKey);
        String cipherText = base64Encode(cipherBytes, base64Type);
        String urlEncodedCipherText = urlEncode(cipherText);
        return urlEncodedCipherText;
    }
    catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

public static final String ALGORITHM = "RSA";
public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";

public static PublicKey loadPublicKey(String stored, int base64Type) throws GeneralSecurityException {
    String pubKey = stored.replace(BEGIN_PUBLIC_KEY, "");
    pubKey = pubKey.replace(END_PUBLIC_KEY, "");

    byte[] data = Base64.decode(pubKey, base64Type);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
    KeyFactory fact = KeyFactory.getInstance(ALGORITHM);
    PublicKey pub = fact.generatePublic(spec);
    return pub;
}

public static byte[] encrypt(String text, PublicKey key) {
    byte[] cipherText = null;
    try {
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        cipherText = cipher.doFinal(text.getBytes());
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    return cipherText;
}

public static String base64Encode(byte[] cipherBytes, int base64Type){
    byte[] base64Cipher = Base64.encode(cipherBytes, base64Type);
    return new String(base64Cipher);
}

public static String urlEncode(String text){
    return text.replace("+", "-").replace("/", "_").replace("=", ",");
}

Java Code

private void exampleMethod(){
    String pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjS7T3WJ+VLucnUP5WYryeg+hhOjZZl5VxwvJAgo4GrXaXdernTPtmXnOSUlbhd928QRCip7D3rLwJNvGIwhPa6coA+YQnj+aHQC02AvCJP/9jpeNmm5MASZfYFXrdmOrMhAPpDZ4rUk1mqtvpwBkYmW3VbMtG336wT1bAIKPHCZuI2n6glupJvs8gK0NuIoAPRlxiQmQD7NCcRx1Et4JmqOMIRC+HqdGv9GGqC/0PB0Fv6LXi8GdzJQPMdoRLR0rvVykNeIzmcimejoIVjI78XUZeB1hF7p55h6W4C4Xm/PrnzKuXw4lBVehZtRhyIvNO62G/eNEZ3tup1/m+vkzHQIDAQAB";
    String privKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCNLtPdYn5Uu5ydQ/lZivJ6D6GE6NlmXlXHC8kCCjgatdpd16udM+2Zec5JSVuF33bxBEKKnsPesvAk28YjCE9rpygD5hCeP5odALTYC8Ik//2Ol42abkwBJl9gVet2Y6syEA+kNnitSTWaq2+nAGRiZbdVsy0bffrBPVsAgo8cJm4jafqCW6km+zyArQ24igA9GXGJCZAPs0JxHHUS3gmao4whEL4ep0a/0YaoL/Q8HQW/oteLwZ3MlA8x2hEtHSu9XKQ14jOZyKZ6OghWMjvxdRl4HWEXunnmHpbgLheb8+ufMq5fDiUFV6Fm1GHIi807rYb940Rne26nX+b6+TMdAgMBAAECggEAOHrlUwmWFHvBmcCZvlKx0lbyfONSJXvTwP9b+K7x5u2dYDFpfEhL4zwxZGwuaw4M3TqhDCeboDnhjD1HtLgcXarPfU/KkiRLmRKxRkTd9ENcwnCqu38odMVPHpEA06nn0O1P9Je+C0TgZvHyhtLHVf3vLB+0Ce2KJUhQYZHZgp7dBt0jgsGlEvQTtc1q3UtElNYbcPdLhg16Dcvw93+C+CWizUxHKl2VAxYBz5fGBxa+Cc6xbj4OGd0mvK9Z+71cX8sgp+WIdSV3a2SZrwYq8XxdtJJfdOwPc6LE56Ul9y3fE/biG8DM8ysu7g4C2K1tjKBTq1Rzz4NBCaGwkWQwGQKBgQDCX5nOiHYi0OYoE3WEVYVuEaf/YljurMiU4xYsy3bXzI42S+iKfVHz+/tvTrXSE0n6AdJnDHyP7mncFZodaurYR90S9VW3P+aOGoysmOmBRWW30xyIZ1g5+PJ9xPzoBfyc/AGHO5w5roY7Gg5myUC+UbucFU+9/JHUVoGV8yFz+wKBgQC58f8iY37sXf/sguv0YHpUV1suPWT90kxC25eRQz0lHWIIw4gfGXI7Tf2vtx7hUPK1z3X9lPiTsqmZIRKPrvykD8djmpfZEOzZlLxxhHUZMZppx19xSsgHaespCZir68aytBWn7FOVpehJP7MWaJurg03V8Cv0NSPU5JhN/j8xxwKBgQCkb6I78oAWtilvz/6EJKGL244HZJkd2bibFH4HCV4R9HB/CLrCpoB1a0BsCueQwFa+FVp9aTfbv/N4iCHoLzJcJHeneTu5mmqe32ERBw/MF/yUhhnGX79o0+25brQSSjZKTHuyf0CMH9RZHZL/a9TE7XpM8k6SyKBKRaC9TYGIjwKBgDZG10x4coIYZi3pgWqSBuM7pJf4v9P/YNdbNgDm+aAt1YHtYXyCdv+solJ6R39Jm69KYYylwXGLg0n5h2t9jq1tFayTYaOw9xIEAoW4Pl4eRo597fQp+f2AA261KGV2q0danb+okjVqekV3XJU778S+zSeXtZzRLiZkm7iYcGXdAoGAUEbRAsopG+blDBmuC5Fhr7Grof9lw/zRgfl516n9oujOmDY8Sl4F7jHkgKzOL1CpvMbRswr6G3tmpJMYGNy0HVX4n4QAiIvEraLUtLlh7LotUiU8zzjniK7HPANoOsQxEi6iUR6jWRf0LKgckE9aaUJjvljj1Hr4PbFpny2gjD4=";

    String message = "This actually works in Java AND Android!";
    String encryptedMessage = getUrlEncodedCipherText(message, pubKey);

    try {
        byte[] base64Decoded = Base64.decodeBase64(encryptedMessage.getBytes(Charset.forName("UTF-8")));
        String decryptedMessage = decrypt(base64Decoded, loadPrivateKey(privKey));
        System.out.println("decryptedMessage: " + decryptedMessage);
        /**
         * This works! Ciphertext always comes out different, as expected, and decodes successfully.
         */
    }
    catch (Exception e){
        e.printStackTrace();
    }
}

public static String getUrlEncodedCipherText(String plainText, String pubKey){
    try {
        final PublicKey publicKey = loadPublicKey(pubKey);
        final byte[] cipherBytes = encrypt(plainText, publicKey);
        String cipherText = base64Encode(cipherBytes);
        String urlEncodedCipherText = urlEncode(cipherText);
        return urlEncodedCipherText;
    }
    catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

public static final String ALGORITHM = "RSA";
public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";

public static PublicKey loadPublicKey(String stored) throws GeneralSecurityException {
    String pubKey = stored.replace(BEGIN_PUBLIC_KEY, "");
    pubKey = pubKey.replace(END_PUBLIC_KEY, "");

    byte[] data = Base64.decodeBase64(pubKey);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
    KeyFactory fact = KeyFactory.getInstance(ALGORITHM);
    PublicKey pub = fact.generatePublic(spec);
    return pub;
}

public static byte[] encrypt(String text, PublicKey key) {
    byte[] cipherText = null;
    try {
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        cipherText = cipher.doFinal(text.getBytes());
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    return cipherText;
}

public static String decrypt(byte[] encrypted, PrivateKey key) {
    byte[] decryptedText = null;
    try {
        final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        decryptedText = cipher.doFinal(encrypted);
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return new String(decryptedText);
}

public static String base64Encode(byte[] cipherBytes){
    byte[] base64Cipher = Base64.encodeBase64(cipherBytes);
    return new String(base64Cipher);
}

public static String urlEncode(String text){
    return text.replace("+", "-").replace("/", "_").replace("=", ",");
}