Search code examples
c#phpcryptographybouncycastlepyopenssl

Can't verify PyOpenSSL signature in C#


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.


Solution

  • 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;
    }