简体   繁体   中英

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

I was following 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 about signing data with SHA256 in .NET 4.0.

One of the answers from that post helped me figure out how to properly generate the digital signature by explicitly loading a SHA256-capable crypto provider without relying on an exportable private key (see the code towards the bottom of the following method where the RSACryptoServiceProvider is constructed):

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

It may be worth noting that I am using a self-signed cert from makecert.exe. According to another answer in that same post, these problems wouldn't occur if I included the proper -sp or -sy flags in makercert.exe. However, even after specifying either of these flags (currently using -sy 24 ) I still have to perform the workaround.

This implementation differs slightly from the accepted answer in that post (again, as our private key is not exportable). But that answer does indicate that the verification can be done without explicitly loading a SHA256-capable crypto provider. Thus, I should have been able to do this before returning in the method above:

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;

However, this won't verify (BTW, I've tried both SignData/VerifyData and SignHash/VerifyHash ). The only way I can get it to verify is if I again explicitly load a SHA256-capable crypto provider. Unfortunately, the KeyContainerName member of the CspKeyContainerInfo constructed from a public certificate is always null. Thus, the only way I can get the data to verify is if I cache (or hard-code) the private key's KeyContainerName. Hence the reason for the mKeyContainerName field in the above method and snippet below:

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

This does verify, but I don't like having to hard-code the private key's KeyContainerName . The private key won't be available on the machine that is doing the verification.

Does anyone know of a better way to accomplish this? Thanks!

I stumbled on my own answer quite by accident. Turns out using a self-signed cert created with makecert.exe is the culprit here. If I use a cert created with OpenSSL or a commercial cert, I no longer have to explicitly load a SHA256-capable crypto provider. Thus I don't have to hard code the container name in the CspParameters object used to instantiate the RSACryptoServiceProvider. This signing code now works as expected:

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

The same is true on the verify side:

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

I never found out what was different about the cert generated by makecert.exe, but it was not important for me as we are signing with a commercial cert now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM