I am trying to verify the authenticity of a message across PHP (phpseclib) and C#.
However, there seem to be some issues with the verification on the C# side.
What I have tried :
PHP Code :
//Load private key
$rsa = PublicKeyLoader::load("... private key ...", false);
//Set PKCS1 mode
$rsa->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1)->withHash("sha256");
//Generate and Convert the hash from 64 byte hex to 32 byte
$hash = pack("H*", hash("sha256", "test"));
//Sign the hash and encode it
$signed_hash = base64_encode($rsa->sign($hash));
//Encode the hash
$hash = base64_encode($hash);
C# Code :
static void Main()
{
//Create a new instance of RSA.
using (RSA rsa = RSA.Create())
{
//Load public key from XML string
rsa.FromXmlString("... public key ...");
//Create an RSAPKCS1SignatureDeformatter object and pass it the RSA instance
//to transfer the key information.
RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(rsa);
RSADeformatter.SetHashAlgorithm("SHA256");
//decode the hash
var hash = Convert.FromBase64String("SHA256 Hash As Base64");
var signedHash = Convert.FromBase64String("SHA256 Hash Signature As Base64");
//Verify the hash and display the results to the console.
if (RSADeformatter.VerifySignature(hash, signedHash))
{
Console.WriteLine("True");
}
else
{
Console.WriteLine("False");
}
}
}
There are issues in both codes:
By default phpseclib uses PSS as padding (here and in the following v3 assumed). Padding is explicitly set to PKCS#1 v1.5 though, but it is not used because the return value is not applied. For the padding change to take effect, the return value must be considered.
By the way, SHA256 is the default here. And RSA::ENCRYPTION_PKCS1
is ignored in the context of signing/verifying and can be removed as well.
phpseclib hashes the data implicitly when signing. Since in the current phpseclib code the hash is additionally generated explicitly, double hashing is done. This double hashing is not necessary and can be removed.
The double Base64 encoding at the end is also unnecessary and can be removed as well.
Overall:
...
//Set PKCS1 mode
$rsa = $rsa->withPadding(RSA::SIGNATURE_PKCS1)->withHash("sha256");
//Sign the hash and encode it
$signature = base64_encode($rsa->sign("test"));
print($signature . PHP_EOL);
PKCS#1v1.5 is deterministic, i.e. for the same input data, repeated signing results in the same signature.
Unlike the PHP code, no implicit hashing is done in the C# code, so that must happen explicitly.
The verification with the C# code is then possible with:
...
var hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("test"));
var signature = Convert.FromBase64String("<Base64 encoded signature from PHP code>");
Console.WriteLine(RSADeformatter.VerifySignature(hash, signature)); // True