title: How can I (properly) verify a file using RSA and SHA256 with .NET?
body: <p>I was following <a href="http://blogs.msdn.com/b/alejacma/archive/2008/06/25/how-to-sign-and-verify-the-signature-with-net-and-a-certificate-c.aspx?PageIndex=1" rel="nofollow noreferrer">this great tutorial</a> 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 <a href="https://stackoverflow.com/questions/7444586/how-can-i-sign-a-file-using-rsa-and-sha256-with-net">this SO question</a> about signing data with SHA256 in .NET 4.0.</p>
  
  <p><a href="https://stackoverflow.com/a/27637121">One of the answers</a> 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 <strong>RSACryptoServiceProvider</strong> is constructed):</p>
  
  <pre><code>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"));
  }
  </code></pre>
  
  <p>It may be worth noting that I am using a self-signed cert from makecert.exe. According to <a href="https://stackoverflow.com/a/11223973">another answer</a> in that same post, these problems wouldn't occur if I included the proper <strong>-sp</strong> or <strong>-sy</strong> flags in makercert.exe. However, even after specifying either of these flags (currently using <strong>-sy 24</strong>) I still have to perform the workaround.</p>
  
  <p>This implementation differs slightly from the <a href="https://stackoverflow.com/a/7475985">accepted answer</a> 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:</p>
  
  <pre><code>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;
  </code></pre>
  
  <p>However, this won't verify (BTW, I've tried both <strong>SignData/VerifyData</strong> and <strong>SignHash/VerifyHash</strong>). The only way I can get it to verify is if I again explicitly load a SHA256-capable crypto provider. Unfortunately, the <strong>KeyContainerName</strong> member of the <strong>CspKeyContainerInfo</strong> 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 <strong>mKeyContainerName</strong> field in the above method and snippet below:</p>
  
  <pre><code>// 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();
  </code></pre>
  
  <p>This does verify, but I don't like having to hard-code the private key's <strong>KeyContainerName</strong>. The private key won't be available on the machine that is doing the verification.</p>
  
  <p>Does anyone know of a better way to accomplish this? Thanks!</p>