簡體   English   中英

如何通過 .NET 使用 RSA 和 SHA256 對文件進行簽名?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM