使用X509私钥对dotnet core v2中的数据进行签名(SHA256)

时间:2017-11-07 12:45:05

标签: c# .net 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;
}

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

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

修改

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

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");
        }
    }
}

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

编辑2

为了澄清这两个代码片段正在尝试:

  1. 使用 X509 证书的私钥 RSA-FULL 且无法使用 SHA256 编码进行签名
  2. 创建新私钥 RSA-AES 使用 SHA256 编码进行签名
  3. 将您的X509私钥导入此新私钥
  4. 使用此私钥签署所需数据。
  5. 在.NEt4.5和dotnet core v2.0中尝试同样的事情时会出现复杂情况。

    似乎 框架,库和操作系统之间存在差异。 This answer声明RSACryptoServiceProvider对象依赖于.NET 4.5中软件所在机器的 CryptoAPI ,而this informative post显示了这一点的差异在不同的环境/框架中实现。

    我仍然在研究基于这些信息的解决方案,但遗留下来的核心问题,即上面使用此dotnet核心的签名数据无法通过.NET 4.5实现进行验证。

2 个答案:

答案 0 :(得分: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");
        }
    }
}

签名代码(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;
}

助手类

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

答案 1 :(得分:1)

如果你必须坚持使用4.5,那么你的.NET Framework代码就像它一样好。 (好吧,你可以消除XML格式的使用,直接使用ExportParameters

在.NET 4.6中,问题解决了{-1}}属性的软弃用(这只是告诉我StackOverflow上的每个人都不使用它):

PrivateKey

这与您应为.NET Core(所有版本)编写的代码相同。重构的部分原因是让人们离开using (RSA rsa = certificate.GetRSAPrivateKey()) { return rsa.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } 类型,这在非Windows系统上运行不佳。

验证码为

RSACryptoServiceProvider

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