Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an X509 private key to sign data in dotnet core v2 (SHA256)

I'm having trouble reproducing some cryptographic functionality in dotnet core v2.0. This is code ported from a .NET 4.5 project

.NET 4.5 code

public byte[] SignData(byte[] dataToSign, X509Certificate2 certificate)
{
    var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
    var xml = certificate.PrivateKey.ToXmlString(true);
    rsaCryptoServiceProvider.FromXmlString(xml);
    var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, CryptoConfig.MapNameToOID("SHA256"));
    return signedBytes;
}

In dotnet core the ToXmlString() and FromXmlString() methods are not implemented, so I used a helper class workaround. Aside from that the dotnet core implementation works but, given the same input data and certificate it produces a different outcome.

dotnet core v2.0 code

public byte[] SignData(byte[] dataToSign, X509Certificate2 certificate)
{
    var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
    var rsa = (RSA)certificate.PrivateKey;
    var xml = RSAHelper.ToXmlString(rsa);
    var parameters = RSAHelper.GetParametersFromXmlString(rsa, xml);
    rsaCryptoServiceProvider.ImportParameters(parameters);
    SHA256 alg = SHA256.Create();
    var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, alg);
    return signedBytes;
}

EDIT

The dotnet core signed data fails a signature verification check in the .NET 4.5 codebase. Theoretically it should make no difference what the signing method was, so this should work but doesn't.

public void VerifySignature(byte[] signedData, byte[] unsignedData, X509Certificate2 certificate)
    using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key)
    {
        if (rsa.VerifyData(unsignedData, CryptoConfig.MapNameToOID("SHA256"), signedData))
        {
            Console.WriteLine("RSA-SHA256 signature verified");
        }
        else
        {
            Console.WriteLine("RSA-SHA256 signature failed to verify");
        }
    }
}

Does anyone know if there are compatibility issues between the two methods of signing data?

EDIT 2

For clarification this is what both code snippets are attempting:

  1. Taking a X509 certificate's private key which is RSA-FULL and cannot sign using SHA256 encoding
  2. Creating a new private key which is RSA-AES which can sign using SHA256 encoding
  3. Import your X509 private key into this new private key
  4. Signing the required data using this private key.

The complication comes when attempting the same thing in .NEt4.5 and dotnet core v2.0.

Seems there are differences between frameworks, libraries and OS's. This answer states that the RSACryptoServiceProvider object relies on the CryptoAPI of the machine the software is on in .NET 4.5, and this informative post shows you the difference on how this is implemented in different environments/frameworks.

I'm still working on a solution based on this information but am left the central issue, namely that signed data using this dotnet core above cannot be verified by the .NET 4.5 implementation.

like image 822
Steve Westwood Avatar asked Nov 07 '17 12:11

Steve Westwood


1 Answers

If you MUST stick with 4.5, your .NET Framework code is as good as it gets. (Well, you could eliminate the usage of the XML format and just use ExportParameters directly)

In .NET 4.6 the problem was solved with the soft-deprecation (which just means I tell everyone on StackOverflow to not use it) of the PrivateKey property:

using (RSA rsa = certificate.GetRSAPrivateKey())
{
    return rsa.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}

This is the same code you should write for .NET Core (all versions). Part of the reason for the refactoring was to get people off of the RSACryptoServiceProvider type, which doesn't work well on non-Windows systems.

The verification code would be

using (RSA rsa = certificate.GetRSAPublicKey())
{
    return rsa.VerifyData(
        dataToSign,
        signature,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1);
}

Much less code, stronger type-safety, doesn't have the PROV_RSA_FULL problem, no key exporting/importing...

like image 200
bartonjs Avatar answered Sep 20 '22 00:09

bartonjs