C# 证书签名在服务器上生成不同的签名

C# 证书签名在服务器上生成不同的签名,c#,certificate,signature,sha256,C#,Certificate,Signature,Sha256,我正在尝试使用证书私钥对某些数据进行签名。我发现的问题是,签名是不同的,这取决于我是在本地还是在服务器上执行它 我使用以下代码作为测试,在本地和服务器上的同一用户下运行: using System; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; namespace TestSignature { class Progr

我正在尝试使用证书私钥对某些数据进行签名。我发现的问题是,签名是不同的,这取决于我是在本地还是在服务器上执行它

我使用以下代码作为测试,在本地和服务器上的同一用户下运行:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace TestSignature
{
    class Program
    {
        static void Main(string[] args)
        {

            var key = SigningKeyFromCertificate(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, "thumbprint");
            var alg = CryptoConfig.MapNameToOID("SHA256");
            var data = Encoding.UTF8.GetBytes("test");
            var sig = key.SignData(data, alg);

            Console.WriteLine(Convert.ToBase64String(sig));

        }

        private static RSACryptoServiceProvider SigningKeyFromCertificate(StoreName storeName, StoreLocation storeLocation, X509FindType findType, string findValue)
        {
            X509Store store = new X509Store(storeName, storeLocation);
            store.Open(OpenFlags.ReadOnly);

            var certs = store.Certificates.Find(findType, findValue, false);
            if (certs?.Count > 0)
            {
                var cert = certs[0];
                if (cert.HasPrivateKey)
                {
                    // Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
                    var key = cert.PrivateKey as RSACryptoServiceProvider;
                    var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
                    var parameters = new CspParameters(enhanced.ProviderType, enhanced.ProviderName, key.CspKeyContainerInfo.UniqueKeyContainerName);
                    return new RSACryptoServiceProvider(parameters);
                }
                else
                {
                    throw new Exception($"No private key access to cert '{findValue}.'");
                }
            }
            else
            {
                throw new Exception($"Cert '{findValue}' not found!");
            }
        }
    }
}
在本地,我得到以下签名:

YUjspKhLl7v3u5VQkh1PfHytMTpEtbAftxOA5v4lmph3B4ssVlZp7KedO5NW9K5L222Kz9Ik9/55NirS0cNCz/cDhEFRtD4daJ9qLRuM8oD5hCj6Jt9Vc6WeS2he+Cqfoylnv4V9plfi1xw8y7EyAf4C77BGkXOdyP5wyz2Xubo=
在服务器上,我得到的是这个:

u1RUDwbBlUpOgNNkAjXhYEWfVLGpMOa0vEfm6PUkB4y9PYBk1lDmCAp+488ta+ipbTdSDLM9btRqsQfZ7JlIn/dIBw9t5K63Y7dcDcc7gDLE1+umLJ7EincMcdwUv3YQ0zCvzc9RrP0jKJManV1ptQNnODpMktGYAq1KmJb9aTY=
你知道会有什么不同吗?我认为,使用相同的证书、相同的代码和相同的数据,签名应该是相同的


(该示例是用C#4.5.2编写的。)

您有一些代码可以在
PROV#u RSA_AES
下重新打开CAPI密钥句柄:

// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;

var parameters = new CspParameters(
    enhanced.ProviderType,
    enhanced.ProviderName,
    key.CspKeyContainerInfo.UniqueKeyContainerName);

return new RSACryptoServiceProvider(parameters);
但是
key.CspKeyContainerInfo.UniqueKeyContainerName
不是密钥的名称(它是密钥所在磁盘上的文件名),因此您打开了一个全新的密钥(您还生成了一个新的临时密钥,只是为了询问默认提供程序是什么)。因为它是一个命名的密钥,所以它会持续存在,后续的应用程序执行会解析为同一个密钥——但在每台计算机上都有一个不同的“相同”密钥

重新打开密钥的一种更稳定的方法是

var cspParameters = new CspParameters
{
    KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
    Flags = CspProviderFlags.UseExistingKey,
};
(由于未指定提供程序类型和名称,它们将使用默认值,如果引用不存在的键,则通过说
UseExistingKey
会得到一个异常)



也就是说,最简单的修复方法是停止使用
rsacyptoserviceprovider
。NET Framework 4.6(和.NET Core 1.0)在
X509Certificate2
上有一个(n扩展)方法,
GetRSAPrivateKey()
,它返回一个
RSA
(应该避免强制转换),通常是
RSACng
(在Windows上),但如果只有CAPI有HSM所需的驱动程序,它可能是
rsaccryptoServiceProvider
,将来可能会有其他RSA。由于
RSACng
更好地处理SHA-2,因此几乎不需要“重新打开”返回对象(即使它是
rsaccryptoserviceprovider
,并且即使类型不是
PROV_RSA_AES
(24),这并不意味着HSM将无法执行SHA-2)。

它们是否都解密到相同的值,“test”?签名是否使用
证书[0].PublicKey.Key验证?一个(或两个)私钥引用可能已损坏/不匹配。(您还可以检查证书是否相同,
Convert.ToBase64String(certs[0].RawData))
)@bartonjs:如果我使用
cert.PublicKey.Key作为RSACryptServiceProvider
(或使用
CspParameters
重建)来验证数据,则它在本地和服务器上都会失败。这是否意味着两个位置都有不匹配的私钥引用?@zimdanen yep。我想这是因为你使用了UniqueContainerName而不是ContainerName。您还需要设置ExistingOnly标志,因为这会告诉您,您在第一次运行密钥(而不是打开)时创建了一个密钥,但现在它已保存,因此您将再次打开同一个随机密钥。(真正的答案是升级到较新的.NET并使用GetRSAPrivateKey,避免重新打开它)@zimdanen GetRSAPrivateKey应该永远不需要修改。很大程度上是因为它在大多数情况下都避免使用加密服务提供商。