如何使用RSA和SHA256与。net签署文件?

时间:2021-10-02 18:24:33

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指定无效的算法。。

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. 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? I'm assuming, if it can be done in Java then it can be done in C#.

我如何使用Bouncy Castle OpenSSL来实现这一点。网,安全。密码学,还是我没听说过的第三方图书馆?我假设,如果可以在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. Is that Correct?

问题是我得到的结果和原来的工具不一样。从读取代码中可以看出,进行实际签名的密码服务提供者并没有使用密钥库文件中的PrivateKey。那是正确的吗?

8 个解决方案

#1


46  

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. As per your first example, this is obtained from a call to [CryptoConfig.MapNameToOID](AlgorithmName)1 where AlgorithmName is what you are providing (i.e. "SHA256").

您后面的示例可能不会一直工作,它应该使用散列算法的OID,而不是它的名称。如您的第一个示例所示,这是从对[CryptoConfig.MapNameToOID](AlgorithmName)1的调用中获得的。“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();

#2


18  

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

#3


7  

I settled on changing the key file to specify the appropriate Crypto Service Provider, avoiding the issue in .NET altogether.

我决定修改密钥文件以指定适当的加密服务提供者,从而避免了在. 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“微软增强RSA和AES加密供应商”。

(-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- howto -fix-invalid-算法- - - - -当- - - - - - - - - - - - - - - - - - - - - -

#4


6  

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

#5


5  

When you use a certificate to get your RSACryptoServiceProvider it really matters what's the underlying CryptoAPI provider. By default, when you create a certificate with 'makecert', it's "RSA-FULL" which only supports SHA1 hashes for signature. You need the new "RSA-AES" one that supports SHA2.

当您使用证书获取RSACryptoServiceProvider时,真正重要的是底层加密api提供程序是什么。默认情况下,当您使用“makecert”创建一个证书时,它是“RSA-FULL”,只支持SHA1散列进行签名。你需要新的支持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和AES Cryptographic Provider”(或等效的-sy 24),然后您的代码就可以在不使用密钥的情况下工作了。

#6


3  

According to this blog it should work with FX 3.5 (see note below). 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).

根据这个博客,它应该与FX 3.5一起工作(见下面的说明)。然而,重要的是要记住,大多数。net加密都是基于加密api的(即使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 (i.e. what's working on Windows 7 might not work on Windows 2000).

关键的一点是,加密api算法的支持取决于所使用的加密服务提供者(Crypto Service Provider, 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. That seems to work for some people, YMMV.

阅读评论(来自博客条目),查看在创建RSACCryptoServiceProvider实例时指定AES CSP(而不是默认的CSP)的可能解决方案。这似乎对一些人有用,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. FWIW Mono does not suffer from such issues ;-)

注意:这让很多人感到困惑,因为所有发布的。net框架都包含了SHA256的托管实现,加密api不能使用它。FWIW Mono不受此类问题困扰;-)

#7


2  

Here is how I signed a string without having to modify the certificate (to a Microsoft Enhanced RSA and AES Cryptographic provider).

下面是我如何在不需要修改证书的情况下签署一个字符串(到Microsoft增强型RSA和acryptoes图形提供程序)。

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

#8


0  

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. Installing it into the stored fixed the problem for my scenario and things started working as expected - perhaps you can try that.

我注意到。net中使用了错误的私钥(还是完全错误?)我不记得)当我正在处理的证书不在用户/计算机证书存储。将它安装到存储的解决了我的场景的问题,事情开始像预期的那样工作——也许您可以尝试一下。

#1


46  

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. As per your first example, this is obtained from a call to [CryptoConfig.MapNameToOID](AlgorithmName)1 where AlgorithmName is what you are providing (i.e. "SHA256").

您后面的示例可能不会一直工作,它应该使用散列算法的OID,而不是它的名称。如您的第一个示例所示,这是从对[CryptoConfig.MapNameToOID](AlgorithmName)1的调用中获得的。“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();

#2


18  

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

#3


7  

I settled on changing the key file to specify the appropriate Crypto Service Provider, avoiding the issue in .NET altogether.

我决定修改密钥文件以指定适当的加密服务提供者,从而避免了在. 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“微软增强RSA和AES加密供应商”。

(-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- howto -fix-invalid-算法- - - - -当- - - - - - - - - - - - - - - - - - - - - -

#4


6  

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

#5


5  

When you use a certificate to get your RSACryptoServiceProvider it really matters what's the underlying CryptoAPI provider. By default, when you create a certificate with 'makecert', it's "RSA-FULL" which only supports SHA1 hashes for signature. You need the new "RSA-AES" one that supports SHA2.

当您使用证书获取RSACryptoServiceProvider时,真正重要的是底层加密api提供程序是什么。默认情况下,当您使用“makecert”创建一个证书时,它是“RSA-FULL”,只支持SHA1散列进行签名。你需要新的支持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和AES Cryptographic Provider”(或等效的-sy 24),然后您的代码就可以在不使用密钥的情况下工作了。

#6


3  

According to this blog it should work with FX 3.5 (see note below). 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).

根据这个博客,它应该与FX 3.5一起工作(见下面的说明)。然而,重要的是要记住,大多数。net加密都是基于加密api的(即使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 (i.e. what's working on Windows 7 might not work on Windows 2000).

关键的一点是,加密api算法的支持取决于所使用的加密服务提供者(Crypto Service Provider, 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. That seems to work for some people, YMMV.

阅读评论(来自博客条目),查看在创建RSACCryptoServiceProvider实例时指定AES CSP(而不是默认的CSP)的可能解决方案。这似乎对一些人有用,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. FWIW Mono does not suffer from such issues ;-)

注意:这让很多人感到困惑,因为所有发布的。net框架都包含了SHA256的托管实现,加密api不能使用它。FWIW Mono不受此类问题困扰;-)

#7


2  

Here is how I signed a string without having to modify the certificate (to a Microsoft Enhanced RSA and AES Cryptographic provider).

下面是我如何在不需要修改证书的情况下签署一个字符串(到Microsoft增强型RSA和acryptoes图形提供程序)。

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

#8


0  

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. Installing it into the stored fixed the problem for my scenario and things started working as expected - perhaps you can try that.

我注意到。net中使用了错误的私钥(还是完全错误?)我不记得)当我正在处理的证书不在用户/计算机证书存储。将它安装到存储的解决了我的场景的问题,事情开始像预期的那样工作——也许您可以尝试一下。