证书签名在服务器上产生不同的签名
Certificate signing produces different signature when on server
我正在尝试使用证书私钥签署一些数据。我发现的问题是签名会有所不同,具体取决于我是在本地执行还是在服务器上执行。
我正在使用以下代码作为测试,运行 在本地和服务器上的同一用户下:
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_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
不是密钥的名称(它是磁盘上密钥所在文件的名称),因此您打开了一个全新的密钥(您还生成了一个新的临时密钥只是为了询问默认提供者是什么)。由于它是一个命名的密钥,因此它会持续存在,并且后续的应用程序执行会解析为相同的密钥——但每台计算机上的 "same" 密钥不同。
一种更稳定的重新打开密钥的方法是
var cspParameters = new CspParameters
{
KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
Flags = CspProviderFlags.UseExistingKey,
};
(由于未指定提供者类型和名称,它们将使用默认值,并且通过说 UseExistingKey
如果您引用不存在的键,您会得到一个异常)。
也就是说,最简单的解决方法是停止使用 RSACryptoServiceProvider
。 .NET Framework 4.6(和 .NET Core 1.0)在 X509Certificate2
、GetRSAPrivateKey()
上有一个(n 扩展)方法,它 return 是一个 RSA
(你应该避免转换) 通常是 RSACng
(在 Windows 上),但如果只有 CAPI 具有 HSM 所需的驱动程序,则可能是 RSACryptoServiceProvider
,并且将来可能是其他 RSA。由于 RSACng
更好地处理 SHA-2,因此几乎不需要 "reopen" return 对象(即使它是 RSACryptoServiceProvider
,即使类型不是 PROV_RSA_AES
(24),这并不意味着 HSM 无法执行 SHA-2。
我正在尝试使用证书私钥签署一些数据。我发现的问题是签名会有所不同,具体取决于我是在本地执行还是在服务器上执行。
我正在使用以下代码作为测试,运行 在本地和服务器上的同一用户下:
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_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
不是密钥的名称(它是磁盘上密钥所在文件的名称),因此您打开了一个全新的密钥(您还生成了一个新的临时密钥只是为了询问默认提供者是什么)。由于它是一个命名的密钥,因此它会持续存在,并且后续的应用程序执行会解析为相同的密钥——但每台计算机上的 "same" 密钥不同。
一种更稳定的重新打开密钥的方法是
var cspParameters = new CspParameters
{
KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
Flags = CspProviderFlags.UseExistingKey,
};
(由于未指定提供者类型和名称,它们将使用默认值,并且通过说 UseExistingKey
如果您引用不存在的键,您会得到一个异常)。
也就是说,最简单的解决方法是停止使用 RSACryptoServiceProvider
。 .NET Framework 4.6(和 .NET Core 1.0)在 X509Certificate2
、GetRSAPrivateKey()
上有一个(n 扩展)方法,它 return 是一个 RSA
(你应该避免转换) 通常是 RSACng
(在 Windows 上),但如果只有 CAPI 具有 HSM 所需的驱动程序,则可能是 RSACryptoServiceProvider
,并且将来可能是其他 RSA。由于 RSACng
更好地处理 SHA-2,因此几乎不需要 "reopen" return 对象(即使它是 RSACryptoServiceProvider
,即使类型不是 PROV_RSA_AES
(24),这并不意味着 HSM 无法执行 SHA-2。