C# RSA signing with PSS padding and a MGF1 mask using a SHA-256 hash algorithm

Viewed 1044

First off, I'm still new to crypto/signing so bear with any misuse of terms please.

I need to create a signature in C# that is getting verified by a Python library. In Python, it's a simple chunk of code to decode/verify the signature:

    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.asymmetric import padding

    public_key.verify(signature, payload_contents,
                      padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                                  salt_length=padding.PSS.MAX_LENGTH,
                                  ), hashes.SHA256(), )

My current C# code looks like this:

    private static string CreateSignature(byte[] data, string privateKeyFileLocation)
    {
        var cert = new X509Certificate2(privateKeyFileLocation);
        
        byte[] signedBytes;
        using (var rsa = cert.GetRSAPrivateKey())
        {
            signedBytes = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
        }
        var finalString = Convert.ToBase64String(signedBytes);
        return finalString;
    }

However, the signature is failing the verification check in the Python code. It looks like PSS padding in the .Net libraries defaults to using MGF1. However, I believe I'm having issues due to the mask generation function (MGF1) using a 256-bit hash in the Python code, but defaulting to SHA1 in C#. I've waded through the .Net C# documentation and it looks like there is no way to override this. I looked into Bouncy Castle's C# documentation and am just having trouble finding any kind of similar example of how to set padding with custom params. Does anyone have experience in this area and can lend a few hints?

1 Answers

Just for anyone else dealing with this. This is the code I ended up with.

    private static string GenerateSignatureForData(string data, string privateKeyFileLocation)
    {
        var cert = new X509Certificate2(privateKeyFileLocation, "12345", X509KeyStorageFlags.Exportable);
        var bcCert = TransformRSAPrivateKey(cert.PrivateKey);
        var keyLen = (int)Math.Ceiling((cert.GetRSAPrivateKey().KeySize - 1) / 8.0);

        byte[] signedBytes = CreateSignature(Encoding.ASCII.GetBytes(data), bcCert, keyLen);
        return Convert.ToBase64String(signedBytes);
    }

    private static AsymmetricKeyParameter TransformRSAPrivateKey(AsymmetricAlgorithm privateKey)
    {
        RSACryptoServiceProvider prov = privateKey as RSACryptoServiceProvider;
        RSAParameters parameters = prov.ExportParameters(true);

        return new RsaPrivateCrtKeyParameters(
            new BigInteger(1, parameters.Modulus),
            new BigInteger(1, parameters.Exponent),
            new BigInteger(1, parameters.D),
            new BigInteger(1, parameters.P),
            new BigInteger(1, parameters.Q),
            new BigInteger(1, parameters.DP),
            new BigInteger(1, parameters.DQ),
            new BigInteger(1, parameters.InverseQ));
    }

    private static byte[] CreateSignature(byte[] data, AsymmetricKeyParameter privateKey, int keyLength)
    {
        var digest = new Sha256Digest();
        var saltLength = keyLength - digest.GetDigestSize() - 2;

        PssSigner signer = new PssSigner(new RsaEngine(), new Sha256Digest(), digest, saltLength);
        signer.Init(true, new ParametersWithRandom((RsaPrivateCrtKeyParameters)privateKey));
        signer.BlockUpdate(data, 0, data.Length);
        return signer.GenerateSignature();
    }

There's some extra in there because the salt length also had to be calculated. I had to look at the source code for the crypto library to calculate the salt MAX_LENGTH indicated in the python code here:

    salt_length=padding.PSS.MAX_LENGTH

Here is strictly the code required to do that:

    var cert = new X509Certificate2(privateKeyFileLocation, "12345", X509KeyStorageFlags.Exportable);
    var bcCert = TransformRSAPrivateKey(cert.PrivateKey);
    var keyLen = (int)Math.Ceiling((cert.GetRSAPrivateKey().KeySize - 1) / 8.0);
    var digest = new Sha256Digest();
    var saltLength = keyLength - digest.GetDigestSize() - 2;

Similarly, converting the .Net crypto lib AsymmetricAlgorithm to a Bouncy Castle AsymmetricKeyParameter required the conversion function TransformRSAPrivateKey seen above.

Related