[英]How can I sign a file using RSA and SHA256 with .NET?
我的应用程序将获取一组文件并对其进行签名。 (我不是要对程序集进行签名。)有一个 .p12 文件,我可以从中获取私钥。
这是我尝试使用的代码,但我收到System.Security.Cryptography.CryptographicException "Invalid algorithm specified."
.
X509Certificate pXCert = new X509Certificate2(@"keyStore.p12", "password");
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey;
string id = CryptoConfig.MapNameToOID("SHA256");
return csp.SignData(File.ReadAllBytes(filePath), id);
根据这个答案,它无法完成( RSACryptoServiceProvider
不支持 SHA-256),但我希望可以使用不同的库,例如 Bouncy Castle。
我是这个东西的新手,我发现 Bouncy Castle 非常令人困惑。 我正在将 Java 应用程序移植到 C#,并且必须使用相同类型的加密来对文件进行签名,因此我坚持使用 RSA + SHA256。
我如何使用 Bouncy Castle、OpenSSL.NET、Security.Cryptography 或其他我没有听说过的第三方库来做到这一点? 我假设,如果它可以在 Java 中完成,那么它可以在 C# 中完成。
更新:
这是我从 poupou 的 anwser 中的链接中得到的
X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password");
RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey;
CspParameters cspParam = new CspParameters();
cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName;
cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam);
aescsp.PersistKeyInCsp = false;
byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256");
bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed);
问题是我没有得到与原始工具相同的结果。 据我阅读代码可以看出,进行实际签名的 CryptoServiceProvider 没有使用密钥存储文件中的 PrivateKey。 那是对的吗?
RSA + SHA256 可以并且将会工作......
你后面的例子可能不会一直工作,它应该使用哈希算法的 OID,而不是它的名称。 根据您的第一个示例,这是通过调用CryptoConfig.MapNameToOID(AlgorithmName)
,其中AlgorithmName
是您提供的内容(即“SHA256”)。
首先,您需要的是带有私钥的证书。 我通常通过使用公钥文件 ( .cer
) 从 LocalMachine 或 CurrentUser 存储中读取我的内容来识别私钥,然后枚举证书并匹配哈希...
X509Certificate2 publicCert = new X509Certificate2(@"C:\mycertificate.cer");
//Fetch private key from the local machine store
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach( X509Certificate2 cert in store.Certificates)
{
if (cert.GetCertHashString() == publicCert.GetCertHashString())
privateCert = cert;
}
无论您如何到达那里,一旦您获得了带有私钥的证书,我们就需要重建它。 由于证书创建私钥的方式,这可能是必需的,但我不确定为什么。 无论如何,我们首先导出密钥,然后使用您喜欢的任何中间格式重新导入它,最简单的是 xml:
//Round-trip the key to XML and back, there might be a better way but this works
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
完成后,我们现在可以对一段数据进行签名,如下所示:
//Create some data to sign
byte[] data = new byte[1024];
//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
最后,可以直接使用证书的公钥进行验证,而无需像使用私钥那样进行重构:
key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
throw new CryptographicException();
privateKey.toXMLString(true) 或 privateKey.exportParameters(true) 的使用在安全环境中不可用,因为它们要求您的私钥是可导出的,这不是一个好的做法。
更好的解决方案是显式加载“增强型”加密提供程序,如下所示:
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName);
privKey = new RSACryptoServiceProvider(cspparams);
我是这样处理这个问题的:
X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable);
// This instance can not sign and verify with SHA256:
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
// This one can:
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
byte[] data = Encoding.UTF8.GetBytes("Data to be signed");
byte[] signature = privateKey1.SignData(data, "SHA256");
bool isValid = privateKey1.VerifyData(data, "SHA256", signature);
我决定更改密钥文件以指定适当的 Crypto Service Provider ,完全避免 .NET 中的问题。
因此,当我使用 PEM 私钥和 CRT 公共证书创建 PFX 文件时,我按如下方式执行:
openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx
关键部分是-CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" 。
( -inkey
指定私钥文件, -in
指定要合并的公共证书。)
您可能需要针对手头的文件格式进行调整。 此页面上的命令行示例可以帮助解决此问题: https : //www.sslshopper.com/ssl-converter.html
我在这里找到了这个解决方案: http : //hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/
当您使用证书获取 RSACryptoServiceProvider 时,底层 CryptoAPI 提供程序是什么真的很重要。 默认情况下,当您使用“makecert”创建证书时,它是“RSA-FULL”,它仅支持 SHA1 哈希进行签名。 您需要支持 SHA2 的新“RSA-AES”。
因此,您可以使用附加选项创建证书:-sp "Microsoft Enhanced RSA and AES Cryptographic Provider"(或等效的 -sy 24),然后您的代码将可以在没有关键杂耍的东西的情况下工作。
这是我在无需修改证书的情况下对字符串进行签名的方法(针对 Microsoft 增强型 RSA 和 AES 加密提供程序)。
byte[] certificate = File.ReadAllBytes(@"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx");
X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable);
string stringToBeSigned = "This is a string to be signed";
SHA256Managed shHash = new SHA256Managed();
byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned));
var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider;
RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider();
defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true));
byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256");
string signature = Convert.ToBase64String(signedHashValue);
Console.WriteLine("Signature : {0}", signature);
RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider;
bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue);
Console.WriteLine("Verification result : {0}", verify);
使用可以在更新的框架上使用它。
public byte[] GetSignature(byte[] inputData)
{
using (var rsa = this.signingCertificate.GetRSAPrivateKey())
{
return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
public bool ValidateSignature(byte[] inputData, byte[] signature)
{
using (var rsa = this.signingCertificate.GetRSAPublicKey())
{
return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
上面的signingCertificate
是一个带有私钥的X509Certificate2
。 此方法不需要您导入任何现有的密钥并且在安全环境中工作。
根据此博客,它应该适用于 FX 3.5(请参阅下面的注释)。 然而,重要的是要记住,大多数 .NET 密码学都是基于CryptoAPI 的(即使CNG在最近的 FX 版本中越来越多地暴露出来)。
关键点是CryptoAPI算法支持取决于所使用的加密服务提供程序(CSP),并且在 Windows 版本之间略有不同(即在 Windows 7 上运行的可能不适用于 Windows 2000)。
阅读评论(来自博客条目)以查看在创建RSACCryptoServiceProvider实例时指定 AES CSP(而不是默认 CSP)的可能解决方法。 这似乎对某些人有用,YMMV。
注意:这让很多人感到困惑,因为所有已发布的 .NET 框架都包含 SHA256 的托管实现,CryptoAPI 无法使用该实现。 FWIW Mono不会遇到此类问题;-)
我知道这是一个旧线程,但对于那些仍然停留在过去并寻找答案的人来说,以下基于@BKibler 的答案对我有用。 评论指出它没有使用正确的密钥,这是因为解决方案缺少几个密钥设置。
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
if (!Enum.TryParse<KeyNumber>(privKey.CspKeyContainerInfo.KeyNumber.ToString(), out var keyNumber))
throw new Exception($"Unknown key number {privKey.CspKeyContainerInfo.KeyNumber}");
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName)
{
KeyNumber = (int)keyNumber,
Flags = CspProviderFlags.UseExistingKey
};
privKey = new RSACryptoServiceProvider(cspparams);
您需要同时设置“KeyNumber”和“Flags”,以便使用现有的(不可导出的)密钥,并且您可以使用证书中的公钥进行验证。
当我使用的证书不在用户/计算机证书存储中时,我注意到 .NET 中使用了错误的私钥(或者是完全错误?我不记得)中的类似问题。 将它安装到存储中解决了我的场景中的问题,并且事情开始按预期工作 - 也许您可以尝试一下。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.