【问题标题】:Using an X509 private key to sign data in dotnet core v2 (SHA256)使用 X509 私钥对 dotnet core v2 (SHA256) 中的数据进行签名
【发布时间】:2018-04-19 21:08:56
【问题描述】:

我无法在 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 core 中,ToXmlString()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;
}

编辑

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

为了澄清,这两个代码 sn-ps 都在尝试:

  1. 获取 X509 证书的私钥,该私钥是 RSA-FULL 并且无法使用 SHA256 编码进行签名
  2. 创建一个 新的私钥,它是 RSA-AES可以使用 SHA256 编码进行签名
  3. 将您的 X509 私钥导入此新私钥中
  4. 使用此私钥对所需数据进行签名。

在 .NEt4.5 和 dotnet core v2.0 中尝试相同的事情时会出现复杂情况。

似乎框架、库和操作系统之间存在差异。 This answer 声明 RSACryptoServiceProvider 对象依赖于 .NET 4.5 中软件所在机器的 CryptoAPIthis informative post 向您展示了在不同环境中实现方式的差异/框架。

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

【问题讨论】:

  • 测试签名的正确方法是验证它们。并非所有签名方案都是确定性的。如果没有代码/测试数据,这个问题就被认为是离题了——好吧,无论如何我都会这样做;给出的代码可能应该运行,但我们无法判断是否有任何其他错误。
  • 您的代码,如这里所写,创建一个新密钥,导出它,导入它,并用它签名。所以每次运行你都会有不同的密钥,因此会有不同的签名。另外,不要使用RSACryptoServiceProvider,使用RSA.Create()
  • @MaartenBodewes 我添加了失败的验证步骤以进行澄清。
  • 如果您的RSAHelper 类中有错误怎么办?我们仍然无法调试它。从我们的角度来看:想象一下 you 需要什么来调试它。请注意,SO 一开始并不是真正的在线调试器;通常很难回答“这段代码在某个地方,不知何故是错误的”。
  • 你确定它们没有实现吗?在我看来,他们至少记录在案for .NET core

标签: c# .net cryptography .net-core


【解决方案1】:

解决方案

为了能够在 .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));
    }
}

【讨论】:

  • 这仍然是未经测试的代码,用于 *nix 或 Mac OS 环境。根据我的阅读,可能存在问题。
【解决方案2】:

如果您必须坚持使用 4.5,那么您的 .NET Framework 代码将尽其所能。 (嗯,你可以去掉XML格式的使用,直接使用ExportParameters

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

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

这是您应该为 .NET Core(所有版本)编写的相同代码。重构的部分原因是让人们摆脱 RSACryptoServiceProvider 类型,这在非 Windows 系统上效果不佳。

验证码是

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

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

【讨论】:

  • 这是 dotnet core 的方法,它更容易和可移植,问题是我正在与使用上述 .NET 4.5 方式验证的系统交互,似乎没有在非 Windows dotnet 核心应用程序(例如在 docker 中)上签名数据的方法,该应用程序将由需要 Microsoft AES &amp; RSA Crypto Service Provider 私钥的旧框架成功验证。
  • .NET Core 2.0 使 RSACryptoServiceProvider 类型在非 Windows 系统上工作,只要您避免任何特定于 Windows 的内容(例如询问其名称或按名称加载)。所以 4.5 和更早的代码应该可以工作。如果您的签名在 Core 和 Framework 之间不起作用,则意味着您没有使用相同的密钥;可能您正在读取错误的数据,并且您的导入/导出代码中可能存在错误。
  • 奇怪的是,现在我已将原始 PROV_RSA_FULL 证书 手动 转换为 Microsoft AES &amp; RSA Crypto Service Provider CSP .pfx 以便我可以使用您的上述方法使用 SHA256 对数据进行签名方法 and 在非 Windows 环境中,现在我无法使用上述方法验证 .net 核心中的签名数据。 .NET v4.5中的原始验证码继续验证ok。
  • @SteveWestwood Weird,昨天有人提请我注意一个 CoreFx 说好的签名,而 .NET Framework RSACryptoServiceProvider 说不好,但他们正在手动创建密钥。 GetRSAPublicKey 不应该受私钥的 CSP 影响,所以这里不会发生任何奇怪的事情。欢迎您通过电子邮件向我发送仅包含证书的公共部分(以及数据或数据的哈希值和签名)的复制品。我的 SO 个人资料链接到我的 GH 个人资料,它是可见的。
  • 谢谢,我会努力做到这一点...您可能会看到一些我错过的简单内容:)
猜你喜欢
  • 2014-01-13
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 2018-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-06
相关资源
最近更新 更多