我如何(正确地)通过 .NET 使用 RSA 和 SHA256 验证文件?

How can I (properly) verify a file using RSA and SHA256 with .NET?

我正在关注 this great tutorial on digitally signing/verifying data with .NET. I modified that example code to use SHA256 and hit the "Invalid algorithm specified" exception, which led me to this SO question 关于在 .NET 4.0 中使用 SHA256 签署数据的信息。

One of the answers post 帮助我弄清楚如何通过显式加载支持 SHA256 的加密提供程序而不依赖可导出的私钥来正确生成数字签名(请参阅代码构造 RSACryptoServiceProvider 的以下方法的底部):

static string mKeyContainerName;

static byte[] SignText(string text, string publicCertPath)
{
    // Access Personal (MY) certificate store of current user
    X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);

    // Load the certificate we'll use to verify the signature from a file.
    X509Certificate2 publicCert = new X509Certificate2(publicCertPath);
    publicCert.Verify();
    string publicHash = publicCert.GetCertHashString();

    // Find the certificate we'll use to sign
    X509Certificate2 privateCert = null;
    foreach(X509Certificate2 cert in store.Certificates)
    {
            if(cert.GetCertHashString() == publicHash)
            {
                    // We found it. Get its associated private key
                    privateCert = cert;
                    break;
            }
    }
    store.Close();

    if(privateCert == null)
    {
            throw new Exception("No valid private cert was found");
    }

    // Hash the string
    UnicodeEncoding encoding = new UnicodeEncoding();
    byte[] data = encoding.GetBytes(text);
    SHA256Managed sha256 = new SHA256Managed();
    byte[] hash = sha256.ComputeHash(data);

    // The basic crypto provider only supports SHA-1.
    // Force Enhanced RSA and AES Cryptographic Provider which supports SHA-256.
    RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
    var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
    mKeyContainerName = csp.CspKeyContainerInfo.KeyContainerName;
    var cspparams = new CspParameters
    (
            enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
    );
    csp = new RSACryptoServiceProvider(cspparams);

    // Sign the hash
    return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
}

可能值得注意的是,我使用的是 makecert.exe 的自签名证书。根据同一个 post 中的 another answer,如果我包含正确的 -sp-sy[=41,这些问题就不会发生=] makercert.exe 中的标志。然而,即使指定了这些标志中的任何一个(当前使用 -sy 24),我仍然必须执行解决方法。

此实现与 accepted answer 的细微差别在于 post(同样,因为我们的私钥不可导出)。但是这个答案确实表明可以在不显式加载支持 SHA256 的加密提供程序的情况下完成验证。因此,我应该能够在返回上述方法之前执行此操作:

RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
mKeyContainerName = csp.CspKeyContainerInfo.KeyContainerName;
var cspparams = new CspParameters
(
    enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);

// Sign the hash
byte[] signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));

// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature))
    throw new CryptographicException();

return signature;

但是,这不会验证(顺便说一句,我已经尝试了 SignData/VerifyDataSignHash/VerifyHash).我可以验证它的唯一方法是我是否再次显式加载支持 SHA256 的加密提供程序。不幸的是,从 public 证书构造的 CspKeyContainerInfoKeyContainerName 成员始终为空。因此,我可以获得要验证的数据的唯一方法是缓存(或硬编码)私钥的 KeyContainerName。因此,上述方法和以下代码段中 mKeyContainerName 字段的原因:

// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
cspparams = new CspParameters
(
    enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
if (!csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature))
    throw new CryptographicException();

这确实可以验证,但我不喜欢必须对私钥的 KeyContainerName 进行硬编码。私钥在进行验证的机器上不可用。

有没有人知道更好的方法来完成这个?谢谢!

我偶然发现了自己的答案。原来使用 makecert.exe 创建的自签名证书是这里的罪魁祸首。如果我使用通过 OpenSSL 创建的证书或商业证书,我不再需要显式加载支持 SHA256 的加密提供程序。因此,我不必在用于实例化 RSACryptoServiceProvider 的 CspParameters 对象中对容器名称进行硬编码。此签名代码现在按预期工作:

RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
// Sign the hash
byte[] signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));

验证端也是如此:

// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;

我从来没有发现 makecert.exe 生成的证书有什么不同,但这对我来说并不重要,因为我们现在正在签署商业证书。