2

I'm attempting to generate an RSA SHA512 signature on my server application (written in C#) and verify that signature on a client application (written in Java). Prior to the signature exchange, the server generates a public- and private-key pair and shares the public key with the client app by giving it the modulus and exponent values that were generated. The client app stores the values for later, when the signature needs to be verified.

The server is generating a 128-byte modulus and a 128-byte signature. I'm just using a byte array of [0x00, 0x01, 0x02, 0x03] as my source data for the signature while I'm testing.

For some reason, my Java application always fails when validating the signature.

My C# code (creates the signature):

RSACryptoServiceProvider crypto = new RSACryptoServiceProvider();
crypto.FromXmlString(this.myStoredPrivateKeyXml);

List<byte> signatureBytes = new List<byte>();
signatureBytes.Add(0x00);
signatureBytes.Add(0x01);
signatureBytes.Add(0x02);
signatureBytes.Add(0x03);

byte[] signature = crypto.SignData(signatureBytes.ToArray(), "SHA512");

My Java code (that receives/validates the signature):

byte[] expectedData = new byte[4];
expectedData[0] = 0;
expectedData[1] = 1;
expectedData[2] = 2;
expectedData[3] = 3;

byte[] exponent = getStoredExponent(); // three-byte value from server
byte[] modulus = getStoredModulus(); // 128-byte value from server

RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);

Signature verifier = Signature.getInstance("SHA512withRSA");
verifier.initVerify(publicKey);
verifier.update(expectedData);

if(verifier.verify(signature)) {
  System.out.println("signature verified");
} else {
  System.out.println("signature failed verification"); // always ends up here :(
}

The crypto objects on the C# side are from the System.Security.Cryptography namespace and java.security on the Java side.

The bytes are passed between the apps via web services and base64 strings. I've gone through and printed the values of the byte arrays themselves to make sure the values are correct for the exponent, modulus, and signature between the two applications. Is there any reason why the signature wouldn't be compatible between the two languages/apps? Is there a parameter that I'm missing, maybe? I made them both RSA with SHA512, but perhaps other aspects of the signature need to be accounted for as well?

6
  • The data you sign in C# is 1, 2, 3, 4. The data you claim to be expecting in Java is 0, 1, 2, 3. Commented Apr 23, 2020 at 17:39
  • @PresidentJamesMoveonPolk woops, I updated the sample code to fix that; thanks for the catch. The signature still fails with those arrays as the same, though. Commented Apr 23, 2020 at 18:11
  • 2
    getStoredExponent and getStoredModulus are not defined in the Java code. Assuming that both methods only perform a Base64 decoding of the corresponding data of the key, modulus and exponent are to be interpreted as unsigned values and converted to BigInteger as new BigInteger(1, modulus) or new BigInteger(1, exponent) respectively. With this the verification works on my machine (using my own testkeys). Commented Apr 23, 2020 at 18:41
  • @Topaco you're the most amazing person ever! The 1 parameter on the BigInteger is exactly what was needed. I guess because those values came from C# (where it uses unsigned), I needed that value. Commented Apr 23, 2020 at 18:54
  • Hi @Topaco , can you please explain what is the value of myStoredPrivateKeyXml and how I can generate it? From my understanding, it's signed using private key and verified using public key. How can i get the public key to verify it? Thanks in advance. Commented Jul 7, 2023 at 8:30

1 Answer 1

2

In the XML representation on the C# side, the data are stored as unsigned big endian (and Base64 encoded).

However, the Java BigInteger(byte[] val)-constructor expects the data as signed (two's complement) big endian.

Therefore the BigInteger(int signum, byte[] magnitude)-constructor must be used, which expects the sign in the first parameter and the data in the second parameter as unsigned big endian, i.e. the following change is necessary:

RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent));
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.