简体   繁体   English

如何通过 .NET 使用 RSA 和 SHA256 对文件进行签名?

[英]How can I sign a file using RSA and SHA256 with .NET?

My application will take a set of files and sign them.我的应用程序将获取一组文件并对其进行签名。 (I'm not trying to sign an assembly.) There is a .p12 file that I get the private key from. (我不是要对程序集进行签名。)有一个 .p12 文件,我可以从中获取私钥。

This is the code I was trying to use, but I get a System.Security.Cryptography.CryptographicException "Invalid algorithm specified."这是我尝试使用的代码,但我收到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);

According to this answer it can't be done (the RSACryptoServiceProvider does not support SHA-256), but I was hoping that it might be possible using a different library, like Bouncy Castle.根据这个答案,它无法完成( RSACryptoServiceProvider不支持 SHA-256),但我希望可以使用不同的库,例如 Bouncy Castle。

I'm new to this stuff and I'm finding Bouncy Castle to be very confusing.我是这个东西的新手,我发现 Bouncy Castle 非常令人困惑。 I'm porting a Java app to C# and I have to use the same type of encryption to sign the files, so I am stuck with RSA + SHA256.我正在将 Java 应用程序移植到 C#,并且必须使用相同类型的加密来对文件进行签名,因此我坚持使用 RSA + SHA256。

How can I do this using Bouncy Castle, OpenSSL.NET, Security.Cryptography, or another 3rd party library I haven't heard of?我如何使用 Bouncy Castle、OpenSSL.NET、Security.Cryptography 或其他我没有听说过的第三方库来做到这一点? I'm assuming, if it can be done in Java then it can be done in C#.我假设,如果它可以在 Java 中完成,那么它可以在 C# 中完成。

UPDATE:更新:

this is what I got from the link in poupou's anwser这是我从 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);

       

The problem is that I'm not getting the same results as I got with the original tool.问题是我没有得到与原始工具相同的结果。 As far as I can tell from reading the code the CryptoServiceProvider that does the actual signing is not using the PrivateKey from key store file.据我阅读代码可以看出,进行实际签名的 CryptoServiceProvider 没有使用密钥存储文件中的 PrivateKey。 Is that Correct?那是对的吗?

RSA + SHA256 can and will work... RSA + SHA256 可以并且将会工作......

Your later example may not work all the time, it should use the hash algorithm's OID, rather than it's name.你后面的例子可能不会一直工作,它应该使用哈希算法的 OID,而不是它的名称。 As per your first example, this is obtained from a call to CryptoConfig.MapNameToOID(AlgorithmName) where AlgorithmName is what you are providing (ie "SHA256").根据您的第一个示例,这是通过调用CryptoConfig.MapNameToOID(AlgorithmName) ,其中AlgorithmName是您提供的内容(即“SHA256”)。

First you are going to need is the certificate with the private key.首先,您需要的是带有私钥的证书。 I normally read mine from the LocalMachine or CurrentUser store by using a public key file ( .cer ) to identify the private key, and then enumerate the certificates and match on the hash...我通常通过使用公钥文件 ( .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;
}

However you get there, once you've obtained a certificate with a private key we need to reconstruct it.无论您如何到达那里,一旦您获得了带有私钥的证书,我们就需要重建它。 This may be required due to the way the certificate creates it's private key, but I'm not really sure why.由于证书创建私钥的方式,这可能是必需的,但我不确定为什么。 Anyway, we do this by first exporting the key and then re-importing it using whatever intermediate format you like, the easiest is xml:无论如何,我们首先导出密钥,然后使用您喜欢的任何中间格式重新导入它,最简单的是 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));

Once that is done we can now sign a piece of data as follows:完成后,我们现在可以对一段数据进行签名,如下所示:

//Create some data to sign
byte[] data = new byte[1024];

//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));

Lastly, the verification can be done directly with the certificate's public key without need for the reconstruction as we did with the private key:最后,可以直接使用证书的公钥进行验证,而无需像使用私钥那样进行重构:

key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
    throw new CryptographicException();

The use of privateKey.toXMLString(true) or privateKey.exportParameters(true) aren't usable in a secure environment, since they require your private key to be exportable, which is NOT a good practice. privateKey.toXMLString(true) 或 privateKey.exportParameters(true) 的使用在安全环境中不可用,因为它们要求您的私钥是可导出的,这不是一个好的做法。

A better solution is to explicitly load the "Enhanced" crypto provider as such:更好的解决方案是显式加载“增强型”加密提供程序,如下所示:

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

This is how I dealt with that problem:我是这样处理这个问题的:

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

I settled on changing the key file to specify the appropriate Crypto Service Provider , avoiding the issue in .NET altogether.我决定更改密钥文件以指定适当的 Crypto Service Provider ,完全避免 .NET 中的问题。

So when I create a PFX file out of a PEM private key and a CRT public certificate, I do it as follows:因此,当我使用 PEM 私钥和 CRT 公共证书创建 PFX 文件时,我按如下方式执行:

openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx

The key part being -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" .关键部分是-CSP "Microsoft Enhanced RSA and AES Cryptographic Provider"

( -inkey specifies the private key file and -in specifies the public certificate to incorporate.) -inkey指定私钥文件, -in指定要合并的公共证书。)

You may need to tweak this for the file formats you have on hand.您可能需要针对手头的文件格式进行调整。 The command line examples on this page can help with that: https://www.sslshopper.com/ssl-converter.html此页面上的命令行示例可以帮助解决此问题: https : //www.sslshopper.com/ssl-converter.html

I found this solution here: http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/我在这里找到了这个解决方案: http : //hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/

When you use a certificate to get your RSACryptoServiceProvider it really matters what's the underlying CryptoAPI provider.当您使用证书获取 RSACryptoServiceProvider 时,底层 CryptoAPI 提供程序是什么真的很重要。 By default, when you create a certificate with 'makecert', it's "RSA-FULL" which only supports SHA1 hashes for signature.默认情况下,当您使用“makecert”创建证书时,它是“RSA-FULL”,它仅支持 SHA1 哈希进行签名。 You need the new "RSA-AES" one that supports SHA2.您需要支持 SHA2 的新“RSA-AES”。

So, you can create your certificate with an additional option: -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" (or an equivalent -sy 24) and then your code would work without the key juggling stuff.因此,您可以使用附加选项创建证书:-sp "Microsoft Enhanced RSA and AES Cryptographic Provider"(或等效的 -sy 24),然后您的代码将可以在没有关键杂耍的东西的情况下工作。

Here is how I signed a string without having to modify the certificate (to a Microsoft Enhanced RSA and AES Cryptographic provider).这是我在无需修改证书的情况下对字符串进行签名的方法(针对 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);

Use can use this on more recent frameworks.使用可以在更新的框架上使用它。

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

The signingCertificate above is a X509Certificate2 with a private key.上面的signingCertificate是一个带有私钥的X509Certificate2 This method does not require you to import any existing keys and works in a secure environment.此方法不需要您导入任何现有的密钥并且在安全环境中工作。

According to this blog it should work with FX 3.5 (see note below).根据此博客,应该适用于 FX 3.5(请参阅下面的注释)。 However it's important to recall that most of .NET cryptography is based on CryptoAPI (even if CNG is being more and more exposed in recent FX releases).然而,重要的是要记住,大多数 .NET 密码学都是基于CryptoAPI 的(即使CNG在最近的 FX 版本中越来越多地暴露出来)。

The key point is that CryptoAPI algorithm support depends on the Crypto Service Provider (CSP) being used and that varies a bit between Windows versions (ie what's working on Windows 7 might not work on Windows 2000).关键点是CryptoAPI算法支持取决于所使用的加密服务提供程序(CSP),并且在 Windows 版本之间略有不同(即在 Windows 7 上运行的可能不适用于 Windows 2000)。

Read the comments (from the blog entry) to see a possible workaround where you specify the AES CSP (instead of the default one) when creating your RSACCryptoServiceProvider instance.阅读评论(来自博客条目)以查看在创建RSACCryptoServiceProvider实例时指定 AES CSP(而不是默认 CSP)的可能解决方法。 That seems to work for some people, YMMV.这似乎对某些人有用,YMMV。

Note: this is confusing to many people because all the released .NET frameworks includes a managed implementation of SHA256 which cannot be used by CryptoAPI.注意:这让很多人感到困惑,因为所有已发布的 .NET 框架都包含 SHA256 的托管实现,CryptoAPI 无法使用该实现。 FWIW Mono does not suffer from such issues ;-) FWIW Mono不会遇到此类问题;-)

I know this is an old thread but for those still stuck in the past and looking for an answer, the following worked for me based off @BKibler's answer.我知道这是一个旧线程,但对于那些仍然停留在过去并寻找答案的人来说,以下基于@BKibler 的答案对我有用。 The comments stated it's not using the correct key and it's because the solution is missing a couple key settings.评论指出它没有使用正确的密钥,这是因为解决方案缺少几个密钥设置。

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

You need to set both "KeyNumber" and "Flags" so the existing (non-exportable) key is used and you can use the public key from the certificate to verify.您需要同时设置“KeyNumber”和“Flags”,以便使用现有的(不可导出的)密钥,并且您可以使用证书中的公钥进行验证。

I have noticed similar issues in .NET with the wrong private key being used (or was it flat-out errors? I do not recall) when the certificate I am working with is not in the user/computer certificate store.当我使用的证书不在用户/计算机证书存储中时,我注意到 .NET 中使用了错误的私钥(或者是完全错误?我不记得)中的类似问题。 Installing it into the stored fixed the problem for my scenario and things started working as expected - perhaps you can try that.将它安装到存储中解决了我的场景中的问题,并且事情开始按预期工作 - 也许您可以尝试一下。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM