10

My Android app implements RSA encryption, however the backend can not decrypt the token generated by the app. Here is the code, the beginning and end lines of the public key have been removed before making the calls, what could be the problem?

String encryptedToken = Base64.encodeToString(encrypt(publicKey, "4111111111111111"), Base64.NO_WRAP);

public static byte[] encrypt(String publicKey, String data) {
        if (TextUtils.isEmpty(publicKey) || TextUtils.isEmpty(data)) {
            return null;
        }
        try {
            // Decode the modified public key into a byte[]
            byte[] publicKeyByteArray = Base64.decode(publicKey.getBytes("UTF-8"),Base64.NO_WRAP);

            Cipher mCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyByteArray);
            Key key = keyFactory.generatePublic(x509KeySpec);
            mCipher.init(Cipher.ENCRYPT_MODE, key);
            return mCipher.doFinal(data.getBytes("UTF-8"));
        }
        catch (UnsupportedEncodingException e) {
            Log.e("RSAKEY", e.getMessage());
        }
        catch (NoSuchPaddingException e) {
            Log.e("RSAKEY", e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            Log.e("RSAKEY", e.getMessage());
        } catch (InvalidKeyException e) {
            Log.e("RSAKEY", e.getMessage());
        } catch (InvalidKeySpecException e) {
            Log.e("RSAKEY", e.getMessage());
        } catch (IllegalBlockSizeException e) {
            Log.e("RSAKEY", e.getMessage());
        } catch (BadPaddingException e) {
            Log.e("RSAKEY", e.getMessage());
        }
        return null;
    }

The backend team provided the below sample code that works, but it is for desktop java. Android library does not have the Base64.getEncoder method. it is very similar to what I wrote but mine just does not work.

 // Decode the modified public key into a byte[]
            byte[] publicKeyByteArray = Base64.getDecoder().decode(publicKey.getBytes(StandardCharsets.UTF_8));

            // Create a PublicKey from the byte array
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyByteArray);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey pubKey = keyFactory.generatePublic(keySpec);

            // Get an instance of the Cipher and perform the encryption
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] cipherText = cipher.doFinal(ccNum.getBytes(StandardCharsets.UTF_8));

            // Get the encrypted value as a Base64-encoded String
            String encodeToStr = Base64.getEncoder().encodeToString(cipherText);

            // Print out the encoded, encrypted string
            System.out.println("Encrypted and Encoded String: " + encodeToStr);

I compared the byte array values at every step. The desktop cipher and android cipher got exactly the same inputs. However the results from the Android code cipher.doFinal can not be decrypted by the backend. If I put the desktop results to the REST call body they work fine, so it is not something caused by REST call.

I also tried to create a public/private key pair on Android, and use the generated public key to encrypt instead of using the public key from our backend, and decrypt using the private key and it works. So the cipher is also working, just somehow the backend is expecting something different

9
  • What is the issue? maybe if you show the decryption code someone can answer to this.
    – pedrofb
    Commented Oct 2, 2017 at 19:55
  • Unfortunately I do not have the access to the backend code, it is a 3rd party interface. it returns http 400 error, ERROR:{"status":400,"error":"DecryptionFailure","message":"Could Not Decrypt Payload. Verify 'Public Key' and 'Algorithm'","timeStamp":"2017-10-02T19:22:29.785+0000","trace":null,"type":null,"messages":{"DecryptionFailure":"Could Not Decrypt Payload. Verify 'Public Key' and 'Algorithm'"}}
    – Ray
    Commented Oct 2, 2017 at 20:55
  • So do that. Check the key. Check the algorithm. Check the padding. Check that you're supposed to send base64. Your code appears to be correct.
    – user207421
    Commented Oct 2, 2017 at 22:42
  • Why did you choose "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"? Commented Oct 3, 2017 at 0:28
  • That is what is required. I have been trying all the Base64 flags but none of them worked. The Base64.NO_WRAP is the only one the backend does not complain about wrong base64 format, but still it can not decode it.
    – Ray
    Commented Oct 3, 2017 at 18:09

1 Answer 1

8

Finally someone in the team cracked this. The reason is because the Android OS uses Bouncy castle, the backend uses Sun as the provider, this caused the backend throwing a BadPaddingException. To make it work, the cipher needs to be initialized this way on Android:

 mCipher.init(Cipher.ENCRYPT_MODE, key, new
                    OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1,
                    PSource.PSpecified.DEFAULT));

Check for more details in this post: http://bouncy-castle.1462172.n4.nabble.com/Problems-with-OAEP-SHA-256-hash-crypto-td1466852.html

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