使用X509私钥在dotnet core v2(SHA256)中签名数据

Ste*_*ood 6 .net c# cryptography .net-core

我无法在dotnet core v2.0中重现某些加密功能。这是从.NET 4.5项目移植的代码

.NET 4.5代码

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;
}
Run Code Online (Sandbox Code Playgroud)

在dotnet核心中,ToXmlString()and FromXmlString()方法未实现,因此我使用了一个辅助类解决方法。除此之外,dotnet核心实现还可以工作,但是在给定相同的输入数据和证书的情况下,它会产生不同的结果。

dotnet core v2.0代码

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;
}
Run Code Online (Sandbox Code Playgroud)

编辑

在.NET 4.5代码库中,dotnet核心签名数据未通过签名验证检查。从理论上讲,它应该与什么签名方法没有什么区别,所以这应该起作用,但是没有作用。

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有谁知道两种数据签名方法之间是否存在兼容性问题?

编辑2

为了澄清起见,这是两个代码段都在尝试的内容:

  1. 取得X509证书的RSA-FULL私钥,无法使用SHA256编码进行签名
  2. 创建一个新的私有密钥RSA-AES可以签署使用SHA256编码
  3. 将您的X509私钥导入到这个新的私钥中
  4. 使用此私钥签名所需的数据。

在.NEt4.5和dotnet core v2.0中尝试相同的操作时,会出现麻烦。

似乎还有框架,库和操作系统的差异。该答案表明该RSACryptoServiceProvider对象依赖于.NET 4.5中软件所在计算机的CryptoAPI,并且这篇信息丰富的文章向您展示了如何在不同的环境/框架中实现该差异。

我仍在研究基于此信息的解决方案,但主要的问题是,使用.NET 4.5实施无法验证使用上述dotnet核心的签名数据。

bar*_*njs 5

如果您必须坚持使用 4.5,那么您的 .NET Framework 代码就已经足够好了。(嗯,你可以去掉XML格式的使用,ExportParameters直接使用)

在 .NET 4.6 中,该问题通过PrivateKey属性的软弃用(这只是意味着我告诉 StackOverflow 上的每个人不要使用它)解决了:

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

这与您应该为 .NET Core(所有版本)编写的代码相同。重构的部分原因是为了让人们摆脱这种RSACryptoServiceProvider类型,这在非 Windows 系统上不能很好地工作。

验证码将是

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

更少的代码,更强的类型安全性,没有 PROV_RSA_FULL 问题,没有密钥导出/导入......


Ste*_*ood 2

解决方案

能够在 .NET 4.5 中验证使用 dotnet core v2.0 中的 X509 RSA 私钥签名的数据

验证码(.NET 4.5)

public void VerifySignedData(byte[] originalData, byte[] signedData, X509Certificate2 certificate)
{
    using (var rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key)
    {
        if (rsa.VerifyData(originalData, CryptoConfig.MapNameToOID("SHA256"), signedData))
        {
            Console.WriteLine("RSA-SHA256 signature verified");
        }
        else
        {
            Console.WriteLine("RSA-SHA256 signature failed to verify");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

签名代码(dotnet core v2.0)

private byte[] SignData(X509Certificate2 certificate, byte[] dataToSign)
{
    // get xml params from current private key
    var rsa = (RSA)certificate.PrivateKey;
    var xml = RSAHelper.ToXmlString(rsa, true);
    var parameters = RSAHelper.GetParametersFromXmlString(rsa, xml);

    // generate new private key in correct format
    var cspParams = new CspParameters()
    {
        ProviderType = 24,
        ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    };
    var rsaCryptoServiceProvider = new RSACryptoServiceProvider(cspParams);
    rsaCryptoServiceProvider.ImportParameters(parameters);

    // sign data
    var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

    return signedBytes;
}
Run Code Online (Sandbox Code Playgroud)

助手类

public static class RSAHelper
{
    public static RSAParameters GetParametersFromXmlString(RSA rsa, string xmlString)
    {
        RSAParameters parameters = new RSAParameters();

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(xmlString);

        if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
        {
            foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
            {
                switch (node.Name)
                {
                    case "Modulus": parameters.Modulus = Convert.FromBase64String(node.InnerText); break;
                    case "Exponent": parameters.Exponent = Convert.FromBase64String(node.InnerText); break;
                    case "P": parameters.P = Convert.FromBase64String(node.InnerText); break;
                    case "Q": parameters.Q = Convert.FromBase64String(node.InnerText); break;
                    case "DP": parameters.DP = Convert.FromBase64String(node.InnerText); break;
                    case "DQ": parameters.DQ = Convert.FromBase64String(node.InnerText); break;
                    case "InverseQ": parameters.InverseQ = Convert.FromBase64String(node.InnerText); break;
                    case "D": parameters.D = Convert.FromBase64String(node.InnerText); break;
                }
            }
        }
        else
        {
            throw new Exception("Invalid XML RSA key.");
        }

        return parameters;
    }

    public static string ToXmlString(RSA rsa, bool includePrivateParameters)
    {
        RSAParameters parameters = rsa.ExportParameters(includePrivateParameters);

        return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
            Convert.ToBase64String(parameters.Modulus),
            Convert.ToBase64String(parameters.Exponent),
            Convert.ToBase64String(parameters.P),
            Convert.ToBase64String(parameters.Q),
            Convert.ToBase64String(parameters.DP),
            Convert.ToBase64String(parameters.DQ),
            Convert.ToBase64String(parameters.InverseQ),
            Convert.ToBase64String(parameters.D));
    }
}
Run Code Online (Sandbox Code Playgroud)