如何使用.net的RSA和SHA256签名文件?

本文关键字:SHA256 签名文件 RSA 何使用 net | 更新日期: 2023-09-27 18:08:41

我的应用程序将获取一组文件并对它们进行签名。(我不是想签署一个协议。)有一个。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。

我对这些东西不熟悉,我发现充气城堡非常令人困惑。我正在将Java应用程序移植到c#,我必须使用相同类型的加密来签署文件,所以我坚持使用RSA + SHA256。

我怎么能做到这一点使用Bouncy Castle, OpenSSL。网,安全。密码学,或者另一个我没听说过的第三方库?我认为,如果Java可以做到,那么c#也可以做到。

更新:

这是我从poupou的答案链接中得到的

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。对吗?

如何使用.net的RSA和SHA256签名文件?

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

我决定更改密钥文件以指定适当的加密服务提供商,从而完全避免了。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/

可以在最新的框架中使用。

    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。此方法不需要导入任何现有密钥,并且在安全的环境中工作。

当您使用证书来获取RSACryptoServiceProvider时,真正重要的是底层的CryptoAPI提供者是什么。默认情况下,当您使用'makecert'创建证书时,它是"RSA-FULL",仅支持SHA1哈希签名。你需要新的支持SHA2的"RSA-AES"。

因此,您可以使用额外的选项创建证书:-sp"Microsoft Enhanced RSA and AES Cryptographic Provider"(或等效的- sy24),然后您的代码将在没有密钥杂耍的情况下工作。

这是我如何在不修改证书的情况下签署字符串(到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);

根据这个博客,它应该与FX 3.5一起工作(见下面的注释)。然而,重要的是要记住,大多数。net加密都是基于CryptoAPI(即使CNG在最近的FX版本中越来越多地暴露出来)。

关键点是CryptoAPI算法支持取决于所使用的加密服务提供商 (CSP),这在Windows版本之间略有不同(即在Windows 7上工作的可能不适用于Windows 2000)。

阅读评论(来自博客条目)以查看在创建RSACCryptoServiceProvider实例时指定AES CSP(而不是默认的)的可能的解决方案。这似乎对某些人有用。

注意:这让很多人感到困惑,因为所有发布的。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"answers";Flags"因此使用现有的(不可导出的)密钥,您可以使用证书中的公钥进行验证。

我注意到在。net中使用错误的私钥(或者它是平坦的错误?当我正在使用的证书不在用户/计算机证书存储中时(我不记得了)。将它安装到存储中修复了我的场景中的问题,并且事情开始按预期工作-也许您可以尝试一下。