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

mie*_*ch1 5 .net c# hash rsa digital-signature

我正在关注这个关于使用 .NET 进行数字签名/验证数据的精彩教程。我修改了该示例代码以使用 SHA256 并遇到“指定的算法无效”异常,这让我想到了这个关于在 .NET 4.0 中使用 SHA256 对数据进行签名的问题

该帖子中的一个答案帮助我弄清楚如何通过显式加载支持 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"));
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,我使用的是来自 makecert.exe 的自签名证书。根据同一帖子中的另一个答案,如果我在 makercert.exe 中包含正确的-sp-sy标志,这些问题就不会发生。但是,即使在指定了这些标志中的任何一个(当前使用-sy 24)之后,我仍然必须执行解决方法。

此实现与该帖子中接受的答案略有不同(同样,因为我们的私钥不可导出)。但该答案确实表明无需显式加载支持 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;
Run Code Online (Sandbox Code Playgroud)

但是,这不会验证(顺便说一句,我已经尝试了SignData/VerifyDataSignHash/VerifyHash)。我可以让它验证的唯一方法是我是否再次显式加载支持 SHA256 的加密提供程序。不幸的是,从公共证书构造的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();
Run Code Online (Sandbox Code Playgroud)

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

有谁知道更好的方法来实现这一目标?谢谢!

mie*_*ch1 0

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

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

验证端也是如此:

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

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