C# – Digital Signatures: Encrypting the Hash vs Signing the Hash

c++certificatecryptographyencryption

I'm trying to implement SHA256-RSA digital signatures and I'm confused with the terminology and implementation in C#.

AFAIK, "signing a file" is to generate the hash of a file, and then encrypt that hash. I've also heard the phrase "signing the hash". Is this the same thing? Or is this hashing a hash and then encrypting hash'?

Here's the code in question:

public void SignatureTest(byte[] data, X509Certificate2 cert)
{
    var sha256 = new SHA256CryptoServiceProvider();
    var rsa = (RSACryptoServiceProvider)cert.PrivateKey;

    var hashOfData = sha256.ComputeHash(data);

    var encryptedHash = rsa.Encrypt(hashOfData, false);
    var encryptedHashOAEP = rsa.Encrypt(hashOfData, true);            
    var signedHash = rsa.SignHash(hashOfData, "SHA256");

    //Shouldn't one of these be true?
    var false1 = CompareAsBase64Str(encryptedHash, signedHash);
    var false2 = CompareAsBase64Str(encryptedHashOAEP, signedHash);

    //This is the one that actually matches
    var true1 = CompareAsBase64Str(signedHash, rsa.SignData(data, sha256));
}

public bool CompareAsBase64Str(byte[] b1, byte[] b2)
{
    return (Convert.ToBase64String(b1) == Convert.ToBase64String(b2));
}

Here's what MSDN says on RSACryptoServiceProvider:

SignHash() Computes the signature for the specified hash value by encrypting it with the private key.

Encrypt() Encrypts data with the RSA algorithm.

Shouldnt SignHash(hash) and Encrypt(hash) be the same?

Best Solution

The answer given by zaitsman is a good explanation of the topics related to your questions and I think should be the accepted answer, but just to help tie it back to your specific question of why encrypting the hash doesn't give you the same result as signing the hash (the rsa.SignHash(hashOfData, "SHA256") in your code):

Signing a hash doesn't just encrypt the hash data -- it also encrypts the name (or some identifier) of the hashing algorithm used to generate the hash along with it. Without that, the receiver wouldn't know what algorithm to use when computing their own hash (of the message being sent) to compare with the one they just decrypted in order to verify the authenticity of the message (which, of course, is the whole point).

When you encrypted the hash yourself (with rsa.Encrypt(hashOfData, false) and rsa.Encrypt(hashOfData, true)), you only encrypted the hash data and not the combination of hash data and algorithm identifier ("SHA256" in your code). In other words, you encrypted different data, so you got different (encrypted) results.

The reason the return value of that SignHash call does match the value returned by rsa.SignData(data, sha256) is that the latter method does the same thing, except it does the hashing and hash signing as one operation, so you don't have to compute the hash as a separate step if you don't need it for any purpose other than signing it.

From RSACryptoServiceProvider.SignData Method on MSDN:

Computes the hash value of the specified data and signs it.


Also see: Why does SignHash need to know what hash algorithm was used?

Related Question