0

I can sign and verify data in PHP using OpenSSL:

function generate_signature($privateKey, $data) {

    $keyData = openssl_get_privatekey($privateKey);

    openssl_sign($data, $signature, $keyData, OPENSSL_ALGO_SHA256);
    openssl_free_key($keyData);

    $sigText = base64_encode($signature);
    return $sigText;
}

function verify_signature($pubKey, $sigText, $data) {
    $signature = base64_decode($sigText);       

    $keyData = openssl_get_publickey($pubKey);
    $ok = openssl_verify($data, $signature, $keyData, "sha256WithRSAEncryption");
    openssl_free_key($keyData);

    return $ok;
}

$privateKey= file_get_contents("private.key");
$pubKey = file_get_contents("public.pem");

$data = "This is a test";
$sigText = generate_signature($privateKey, $data);
$result = verify_signature($pubKey, $sigText, $data);

This works great for whatever data values I try. $result is always true.

However, when I try to verify the signature in C# using the BouncyCastle APIs, it always fails:

public static bool VerifySignature(string data, string signatureText)
{
    // Using statements and error checking removed for brevity 

    var uri = new Uri("pack://application:,,,/Resources/public.pem");
    var resourceStream = Application.GetResourceStream(uri);
    var reader = new StreamReader(resourceStream.Stream);

    PemReader pemReader = new PemReader(reader);
    RsaKeyParameters parameters = (RsaKeyParameters)pemReader.ReadObject();
    RSACryptoServiceProvider provider = (RSACryptoServiceProvider)RSACryptoServiceProvider.Create();

    RSAParameters rParams = new RSAParameters();
    rParams.Modulus = parameters.Modulus.ToByteArray();
    rParams.Exponent = parameters.Exponent.ToByteArray();

    provider.ImportParameters(rParams);

    byte[] sigBytes = Convert.FromBase64String(signatureText);
    byte[] dataBytes = Encoding.UTF8.GetBytes(data);

    bool isValid = provider.VerifyData(dataBytes, CryptoConfig.MapNameToOID("SHA256"), sigBytes);

    return isValid;
}

No matter what I try, isValid is always false. I've tried using SHA1 on both ends instead of SHA256, I've tried different encodings, I've tried reversing various byte arrays in case there were some endian issues, but no matter what I try, I can't verify the PHP signature. I'm clearly missing something on the C# side, but I don't know what.

jww
  • 97,681
  • 90
  • 411
  • 885
lfalin
  • 4,219
  • 5
  • 31
  • 57
  • 1
    When something is wrong with a signature there is a trick you can do for debugging: an RSA signature is technically an RSA encrypted hash value. You can simply decrypt it using the public key you use for verify (and using no padding algo). Then save the data, extract the hash and compare it with what you expect. – Robert Aug 05 '16 at 16:53
  • 1) Did you add the certificate in the certificate store? 2) According with this documentation: https://msdn.microsoft.com/en-us/library/55yk5665(v=vs.110).aspx you shoul compute the hash: hash.ComputeHash – Leonidas Menendez Aug 05 '16 at 17:01
  • @LeonidasMenendez I'm pretty sure the certificates don't need to be in the keystore to use them, that's just a secure place to store them. You still have to load the raw key bytes out of them. – lfalin Aug 05 '16 at 18:11

1 Answers1

0

Well, I'm still not sure what RSACryptoServiceProvider has against me, but I found another method that works perfectly:

public static bool VerifySignature(string data, string signatureText)
{
    // Using statements and error checking removed for brevity 
    var uri = new Uri("pack://application:,,,/Resources/public.pem");
    var resourceStream = Application.GetResourceStream(uri);
    var reader = new StreamReader(resourceStream.Stream);

    PemReader pemReader = new PemReader(reader);
    RsaKeyParameters parameters = (RsaKeyParameters)pemReader.ReadObject();

    RsaDigestSigner signer = (RsaDigestSigner)SignerUtilities.GetSigner("SHA-256withRSA");
    signer.Init(false, parameters);

    byte[] sigBytes = Convert.FromBase64String(signatureText);
    byte[] dataBytes = Encoding.UTF8.GetBytes(data);

    signer.BlockUpdate(dataBytes, 0, dataBytes.Length);
    bool isValid = signer.VerifySignature(sigBytes);

    return isValid;
}
lfalin
  • 4,219
  • 5
  • 31
  • 57