57

I recently posted about issues with encrypting large data with RSA, I am finally done with that and now I am moving on to implementing signing with a user's private key and verifying with the corresponding public key. However, whenever I compare the signed data and the original message I basically just get false returned. I am hoping some of your could see what I am doing wrong.

Here is the code:

public static string SignData(string message, RSAParameters privateKey)
    {
        //// The array to store the signed message in bytes
        byte[] signedBytes;
        using (var rsa = new RSACryptoServiceProvider())
        {
            //// Write the message to a byte array using UTF8 as the encoding.
            var encoder = new UTF8Encoding();
            byte[] originalData = encoder.GetBytes(message);

            try
            {
                //// Import the private key used for signing the message
                rsa.ImportParameters(privateKey);

                //// Sign the data, using SHA512 as the hashing algorithm 
                signedBytes = rsa.SignData(originalData, CryptoConfig.MapNameToOID("SHA512"));
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);
                return null;
            }
            finally
            {
                //// Set the keycontainer to be cleared when rsa is garbage collected.
                rsa.PersistKeyInCsp = false;
            }
        }
        //// Convert the a base64 string before returning
        return Convert.ToBase64String(signedBytes);
    }

So that is the first step, to sign the data, next I move on to verifying the data:

public static bool VerifyData(string originalMessage, string signedMessage, RSAParameters publicKey)
    {
        bool success = false;
        using (var rsa = new RSACryptoServiceProvider())
        {
            byte[] bytesToVerify = Convert.FromBase64String(originalMessage);
            byte[] signedBytes = Convert.FromBase64String(signedMessage);
            try
            {
                rsa.ImportParameters(publicKey);

                SHA512Managed Hash = new SHA512Managed();

                byte[] hashedData = Hash.ComputeHash(signedBytes);

                success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA512"), signedBytes);
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                rsa.PersistKeyInCsp = false;
            }
        }
        return success;
    }

And here is the test client:

public static void Main(string[] args)
    {
        PublicKeyInfrastructure pki = new PublicKeyInfrastructure();
        Cryptograph crypto = new Cryptograph();
        RSAParameters privateKey = crypto.GenerateKeys("email@email.com");

        const string PlainText = "This is really sent by me, really!";

        RSAParameters publicKey = crypto.GetPublicKey("email@email.com");

        string encryptedText = Cryptograph.Encrypt(PlainText, publicKey);

        Console.WriteLine("This is the encrypted Text:" + "\n " + encryptedText);

        string decryptedText = Cryptograph.Decrypt(encryptedText, privateKey);

        Console.WriteLine("This is the decrypted text: " + decryptedText);

        string messageToSign = encryptedText;

        string signedMessage = Cryptograph.SignData(messageToSign, privateKey);

        //// Is this message really, really, REALLY sent by me?
        bool success = Cryptograph.VerifyData(messageToSign, signedMessage, publicKey);

        Console.WriteLine("Is this message really, really, REALLY sent by me? " + success);

    }

Am I missing a step here? According to the Cryptography API and the examples there, I shouldn't manually compute any hashes, since I supply the algorithm within the method call itself.

Any help will be greatly appreciated.

Simon Langhoff
  • 1,395
  • 3
  • 18
  • 28
  • 3
    Where do `PublicKeyInfrastructure` and `crypto.GenerateKeys` come from? – bendytree Nov 11 '13 at 22:56
  • The PublicKeyInfrastructure were simply a collection class that handled some serialization for when storing some public key information. The GenerateKeys method was from a helper class that generated a public/private key-pair. I am sorry that this was not clear enough. – Simon Langhoff Jan 20 '14 at 23:55
  • 5
    @SimonLanghoff Why do you compute `hashedData` in VerifyData() and then not use it? Is this a typo? – JoshVarty Mar 21 '15 at 08:06
  • 1
    In VerifyData should you be calling rsa.VerifyHash(hashedData) ? or is this the same as calling rsa.VerifyData(data) ? – Andy May 15 '17 at 14:25
  • 1
    Also see [Signing and verifying signatures with RSA C#](https://stackoverflow.com/q/8437288/608639), [how to sign bytes using my own rsa private key using rs256 algorithm?](https://stackoverflow.com/q/25909044/608639), [Signing data with private key in c#](https://stackoverflow.com/q/31828420/608639), [How can I sign a file using RSA and SHA256 with .NET?](https://stackoverflow.com/q/7444586/608639), [Signing a string with RSA private key on .NET?](https://stackoverflow.com/q/3169829/608639), etc. – jww May 30 '17 at 13:54
  • This tutorial might help: https://medium.com/gitconnected/how-browsers-verify-digital-certificates-part-1-26ee57a6e712 – David Klempfner Feb 26 '22 at 00:16

1 Answers1

34

Your problem is at the beginning of the VerifyData method:

public static bool VerifyData(string originalMessage, string signedMessage, RSAParameters publicKey)
{
    bool success = false;
    using (var rsa = new RSACryptoServiceProvider())
    {
        //Don't do this, do the same as you did in SignData:
        //byte[] bytesToVerify = Convert.FromBase64String(originalMessage);
        var encoder = new UTF8Encoding();
        byte[] bytesToVerify = encoder.GetBytes(originalMessage);

        byte[] signedBytes = Convert.FromBase64String(signedMessage);
        try
        ...

For some reason you switched to FromBase64String instead of UTF8Encoding.GetBytes.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
csharptest.net
  • 62,602
  • 11
  • 71
  • 89
  • 4
    Also, for signing data, you should use the private key, then use the public key to decrypt data that was signed with the private key. This is because you distribute the public key -- not the private key. – Kraang Prime May 11 '15 at 04:14
  • I know there's a maximum string size RSA can encrypt. Is there a max string size RSA can sign as well ? or can it sign a string of any length ? – mrid Apr 28 '19 at 17:13
  • 3
    @KraangPrime wrong. When you sign, you sign with your private key, and then a public party verifies with your corresponding public key, not decrypts. When it comes to encryption, a public party will user your public key to encrypt data, then you decrypt it with your private key – I Stand With Russia Dec 17 '19 at 22:16