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;
Advertisement
Answer
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); ...