1

I am creating an RSA signature in Java and sending it in the Auth header to a PHP server which is then verifying it. The problem is that although the signature is being verified in Java, it is failing in PHP. How do I fix this?

private String getSignature(JsonObject body) {
    try {
        InputStream is  = getClass().getClassLoader().getResourceAsStream("private.key");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        try (InputStreamReader keyReader = new InputStreamReader(is);
          PemReader pemReader = new PemReader(keyReader)) {

            PemObject pemObject = pemReader.readPemObject();
            byte[] content = pemObject.getContent();
            PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
            PrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privKeySpec);
            System.out.println(privKey.getAlgorithm());
            Signature sign = Signature.getInstance("SHA1withRSA");
            sign.initSign(privKey);
            sign.update(body.toString().getBytes());
            verifySignature(body, sign.sign());
            return new String(sign.sign());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

private String verifySignature(JsonObject body, byte[] bs) {
    try {
        InputStream is  = getClass().getClassLoader().getResourceAsStream("public.pem");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        try (InputStreamReader keyReader = new InputStreamReader(is);
          PemReader pemReader = new PemReader(keyReader)) {

            PemObject pemObject = pemReader.readPemObject();
            byte[] content = pemObject.getContent();
            KeySpec privKeySpec = new X509EncodedKeySpec(content);
            PublicKey privKey = (RSAPublicKey) keyFactory.generatePublic(privKeySpec);
            System.out.println(privKey.getAlgorithm());
            Signature sign = Signature.getInstance("SHA1withRSA");
            sign.initVerify(privKey);
            sign.update(body.toString().getBytes());
            boolean res = sign.verify(bs);
            System.out.println(res);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

My PHP code where I'm verifying the sign:

$fp = fopen("public.pem", "r");
$pub_key = fread($fp, 8192);
fclose($fp);
$pubkeyid = openssl_pkey_get_public($pub_key);
$dd = ["email"=>"kkamran@gmail.com","password"=>"!Pass1234","platform"=>"con"];
$ss = base64_decode("dHIG77+9fu+/ve+/ve+/vVTvv73vv70677+9QiHGnmjHu++/vQYm77+977+977+9bXM+Pe+/ve+/vRtlJcyYY++/vdiu77+9fu+/vUkM77+9RQI2BO+/ve+/vX4YFRwt77+9GO+/vWrvv71F77+9C++/ve+/ve+/ve+/vWTvv73vv71zB++/vXFaQ86277+9LHXvv71+Q3Dvv73vv73vv71TEzMo77+9SgLvv73vv73vv70377+9IO+/vVFW0rYc77+9QX8a77+977+977+977+9Iu+/ve+/ve+/vUYkIU5heO+/vc6e77+977+977+9Rmnvv73vv71+PRxy77+9zLTvv73vv71aDe+/vQjvv71UY++/ve+/vUTvv704FyZjBkB/77+977+9fe+/ve+/ve+/ve+/vXJgUAYUVO+/ve+/ve+/vXNE77+9fmAc77+977+9Ye+/vUAKF8izTO+/ve+/vSPvv700Ru+/ve+/ve+/vWHvv70bDtyoNmxpKd2UKe+/ve+/ve+/vXgCLe+/ve+/vQPvv70p77+9S2Xvv73vv71bX++/vUhw77+9Oe+/vV0+77+9De+/vQs=");
$ok = openssl_verify( $data, $ss, $pubkeyid, OPENSSL_ALGO_SHA1);

Update:

I am now signing like this:

Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privKey);
sign.update(body.toString().getBytes());
String signStr = Base64.getEncoder().encodeToString(sign.sign());
verifySignature(body, signStr.getBytes());
return signStr;
khateeb
  • 5,265
  • 15
  • 58
  • 114
  • 1
    The Base64 decoded signature in the PHP code contains many 0xEFBFBD sequences, which corresponds to the replacement character. This typically happens when binary data is UTF-8 decoded, which corrupts the data. In your code, this occurs in the line `return new String(sign.sign())`. Instead, the data should be Base64 encoded `return Base64.getEncoder().encodeToString(sign.sign())`. – Topaco Sep 28 '22 at 12:27
  • 1
    Also, before the second `sign.sign()` call, the `sign.update()` call must be performed again, otherwise an empty message will be signed (of course, it is more efficient to perform the `sign.sign()` call only once). – Topaco Sep 28 '22 at 13:08
  • @Topaco I did what you said and I updated my question. But now when verifying, I am getting a `SignatureException: Signature length not correct: got 344 but was expecting 256` error on the `boolean res = sign.verify(bs);` line. – khateeb Sep 28 '22 at 15:07
  • 1
    Try the following: `byte[] signature = sign.sign(); verifySignature(body, signature); return Base64.getEncoder().encodeToString(signature);` – Topaco Sep 28 '22 at 15:17
  • Yes, this got verified – khateeb Sep 29 '22 at 08:33
  • @Topaco You can put this in an answer and I'll accept it. – khateeb Sep 29 '22 at 08:59
  • My pleasure. I have put my comments in an answer. – Topaco Sep 29 '22 at 11:14

1 Answers1

1

The line:

return new String(sign.sign());

in the getSignature() method performs an decoding of the signature with the default charset. From the posted signature it can be concluded that this is the UTF-8 charset. This UTF-8 decoding corrupts the signature!
Binary data like signatures, ciphertexts, hash values, random binary data etc. generally do not contain UTF-8 compliant byte sequences. When decoding with UTF-8, these non-compliant sequences are replaced by the 0xEFBFBD replacement character, which irreversibly corrupts the data. The 0xEFBFBD byte sequence occurs with high frequency in the posted signature (after Base64 decoding), which is a clear indication of corruption resulting from UTF-8 decoding.
In general, charset encodings such as UTF-8 are not suitable for converting binary data to a string (unless, of course, the binary data was generated using that encoding). Instead, a binary-to-text encoding should be used, e.g. Base64, see also here.

A second problem is the double sign.sign() call in getSignature(). A sign() call resets the state of the signature object to the state immediately after the last initSign(), see here. I.e. in the present case the second call is made without the data from the update() call, so the signature is ultimately created for an empty message. This signature is returned and therefore of course does not correspond to the actual message, so that a later verification will fail.
A possible solution would be to additionally execute the corresponding update() call before the second sign() call.
Of course, in this particular case it is more efficient to execute the sign.sign() call only once and store the result to be able to use it later (as often as needed).

Both problems are successfully fixed as follows:

...
byte[] signature = sign.sign(); 
verifySignature(body, signature); 
return Base64.getEncoder().encodeToString(signature); 
...
Topaco
  • 40,594
  • 4
  • 35
  • 62