1

A Java application verifies that an XML string that came from a clearnet server (no SSL) has not been tampered, using a message digest. It does so with a public key stored locally, accessed with the variable pk_enc

The application asks an external server for 2 strings: the XML and a hash (both encoded base64):

String xml_enc = // ...
String hashStr_enc = // ...

The application then decodes from base64:

String xml = new String(Base64.decode(xml_enc));
byte[] hashStr = Base64.decode(hashStr_enc);

In xml we now have a readable XML string and in hashStr_dec a bunch of unreadable characters.

It then verifies that the result of these 2 functions are equal:

public String createMD5hashForResponseXMLDocument(String xml) {
    try {
        byte[] e = xml.getBytes();
        MessageDigest algorithm = MessageDigest.getInstance("MD5");
        algorithm.reset();
        algorithm.update(e);
        byte[] messageDigest = algorithm.digest();
        StringBuffer hexString = new StringBuffer();

        for (int i = 0; i < messageDigest.length; ++i) {
            hexString.append(Integer.toString((messageDigest[i] & 255) + 256, 16).substring(1));
        }

        return hexString.toString();
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}

and

public String decryptRSAcipherUsedForSigning(String pk_enc, byte[] hashStr) {
    try {

        X509EncodedKeySpec e = new X509EncodedKeySpec(decodeBASE64(pk_enc));
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPublicKey RSApublicKey = (RSAPublicKey) keyFactory.generatePublic(e);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
        cipher.init(2, RSApublicKey);
        byte[] MD5hash = cipher.doFinal(hashStr);
        return new String(MD5hash);

    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}

Everything works as long as you don't touch the XML, and the result of the 2 functions computes as equal.


I would now like to modify the XML and compute the hash correctly (the reverse of what happens above). Given that I have pk_enc it shouldn't be impossible.

How can I do that? Here's what I've tried. For simplicity sake, I used the same XML as the original.

First, I fed the unencoded XML to createMD5hashForResponseXMLDocument:

String xml_md5 = createMD5hashForResponseXMLDocument(my_xml_string);

Then I run this function. It's the same as decryptRSAcipherUsedForSigning but I changed cipher.init(2, RSApublicKey); to cipher.init(1, RSApublicKey);

public byte[] encryptHash(String pk_enc, String xml_m5d) {
        try {
            X509EncodedKeySpec e = new X509EncodedKeySpec(decodeBASE64(pk_enc));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPublicKey RSApublicKey = (RSAPublicKey) keyFactory.generatePublic(e);
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
            cipher.init(1, RSApublicKey);
            byte[] result = cipher.doFinal(xml_m5d.getBytes());
            return result;

        } catch (Exception e) {
            e.printStackTrace();
            return "".getBytes();
        }
    }

This is not working: provided with the same XML I've started with, it does not produce the same result. Furthermore, if I try to to feed it back to decryptRSAcipherUsedForSigning I get this error:

javax.crypto.BadPaddingException: Decryption error
    at sun.security.rsa.RSAPadding.unpadV15(Unknown Source)
    at sun.security.rsa.RSAPadding.unpad(Unknown Source)
    at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
    at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
    at javax.crypto.Cipher.doFinal(Cipher.java:2164)
    at com.test.test.MainTest.decryptRSAcipherUsedForSigning(MainTest.java:112)
    at com.test.test.MainTest.Execute(MainTest.java:59)
    at com.test.test.test.main(test.java:13)

Please note I'm aware of the difference between String and byte[] and I've been careful to not switch inappropriately between the twos. Although I do not exclude that might be the problem, it should be ok.

1

This is impossible to do as long as I don't have the corresponding private key of pk_enc.

Decrypting with a public key means verifying: I didn't know that, saw a public key used for decryption and jumped to the wrong conclusion that it was sufficient to encrypt back the data.

It is not.

| improve this answer | |
  • 1
    You can patch the application to include your RSA key instead of the server and MiTM everything. – sudhackar Nov 3 '18 at 2:44
  • If I could patch the application then I wouldn't even need to meddle with encryption: I could just bypass everything. This problem stems from the fact that I can't. – Saturnix Nov 3 '18 at 3:08
  • I'm reversing Java classes from an old version of the application but the new one is 64-bit wrapped with Jar2Exe. So far, I've found no way to extract Java code from an exe wrapped with Jar2Exe... plenty of tutorials for 32 bit and older versions. – Saturnix Nov 3 '18 at 3:09
  • 1
    @Saturnix You can replace the RSA key in memory. – 0xec Nov 3 '18 at 6:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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