Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Signing a byte array of 128 bytes with RSA in C sharp

I am completely new to cryptography and I need to sign a byte array of 128 bytes with an RSA key i have generated with C sharp. The key must be 1024 bits.

I have found a few examples of how to use RSA with C sharp and the code I'm currently trying to use is:

public static void AssignParameter()
{
    const int PROVIDER_RSA_FULL = 1;
    const string CONTAINER_NAME = "SpiderContainer";
    CspParameters cspParams;
    cspParams = new CspParameters(PROVIDER_RSA_FULL);
    cspParams.KeyContainerName = CONTAINER_NAME;
    cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
    cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
    rsa = new RSACryptoServiceProvider(cspParams);
    rsa.KeySize = 1024;
}

public static string EncryptData(string data2Encrypt)
{
    AssignParameter();
    StreamReader reader = new StreamReader(path + "publickey.xml");
    string publicOnlyKeyXML = reader.ReadToEnd();
    rsa.FromXmlString(publicOnlyKeyXML);
    reader.Close();

    //read plaintext, encrypt it to ciphertext

    byte[] plainbytes = System.Text.Encoding.UTF8.GetBytes(data2Encrypt);
    byte[] cipherbytes = rsa.Encrypt(plainbytes, false);
    return Convert.ToBase64String(cipherbytes);
}

This code works fine with small strings (and thus short byte arrays) but when I try this with a string of 128 characters I get an error saying: CryptographicException was unhandled: Wrong length (OK, it might not precisely say 'Wrong length', I get the error in danish, and that is 'Forkert længde' which directly translates to 'Wrong length').

Can anyone tell me how I can encrypt a byte array of 128 bytes with a RSA key of 1024 bits in C sharp?

Thanks in advance, LordJesus

EDIT:

Ok, just to clarify things a bit: I have a message, from which i make a hash using SHA-256. This gives a 32 byte array. This array is padded using a custom padding, so it ends up being a 128 byte array. This padded hash should then be signed with my private key, so the receiver can use my public key to verify that the message received is the same as the message sent. Can this be done with a key of 1024 bits?

like image 746
Daniel Avatar asked Jan 19 '23 13:01

Daniel


2 Answers

If you want to sign you do not want to encrypt. Signatures and encryption are distinct algorithms. It does not help that there is a well-known signature algorithm called RSA, and a well-known asymmetric encryption algorithm also called RSA, and that the signature algorithm was first presented (and still is in many places) as "you encrypt with the private key". This is just plain confusing.

In RSA encryption, the data to encrypt (with the public key) must be padded with what PKCS#1 (the RSA standard) describes as "Type 2 padding", and the result (which has the same length than the modulus) is then processed through the modular exponentiation which is at the core of RSA (at the core, but RSA is not only a modular exponentiation; the padding is very important for security).

When signing, the data to sign must be hashed, then the hash value is embedded in a structure which describes the hash function which was just used, and the encoded structure is itself padded with a "Type 1 padding" -- not the same padding than the padding for encryption, and that's important, too.

Either way, a normal RSA engine will perform the type 1 or type 2 padding itself, and most RSA signature engines will also handle themselves the structure which identifies the used hash function. A RSA signature engine such as RSACryptoServiceProvider can work either with SignHash(), which expects the hash value (the 32 bytes obtained from SHA-256, without any kind of encapsulating structure or type 1 padding -- RSACryptoServiceProvider handles that itself), or SignData(), which expects the data to be signed (the engine then does the hash computation too).

To sum up, if you do any kind of padding yourself, then you are doing it wrong. If you used Encrypt() to compute a signature, then you are doing it wrong, too.

like image 118
Thomas Pornin Avatar answered Jan 28 '23 12:01

Thomas Pornin


The minimum key size for encrypting 128 bytes would be 1112 bits, when you are calling Encrypt with OAEP off. Note that setting the key size like this rsa.KeySize = 1024 won't help, you need to actually generate they key of the right size and use them.

This is what worked for me:

using System;
using System.IO;
using System.Security.Cryptography;

namespace SO6299460
{
    class Program
    {
        static void Main()
        {
            GenerateKey();
            string data2Encrypt = string.Empty.PadLeft(128,'$');
            string encrypted = EncryptData(data2Encrypt);
            string decrypted = DecryptData(encrypted);

            Console.WriteLine(data2Encrypt);
            Console.WriteLine(encrypted);
            Console.WriteLine(decrypted);
        }

        private const string path = @"c:\";

        public static void GenerateKey()
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1112);
            string publickKey = rsa.ToXmlString(false);
            string privateKey = rsa.ToXmlString(true);

            WriteStringToFile(publickKey, path + "publickey.xml");
            WriteStringToFile(privateKey, path + "privatekey.xml");
        }

        public static void WriteStringToFile(string value, string filename)
        {
            using (FileStream stream = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.Read))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                writer.Write(value);
                writer.Flush();
                stream.Flush();
            }
        }

        public static string EncryptData(string data2Encrypt)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            StreamReader reader = new StreamReader(path + "publickey.xml");
            string publicOnlyKeyXML = reader.ReadToEnd();
            rsa.FromXmlString(publicOnlyKeyXML);
            reader.Close();

            //read plaintext, encrypt it to ciphertext

            byte[] plainbytes = System.Text.Encoding.UTF8.GetBytes(data2Encrypt);
            byte[] cipherbytes = rsa.Encrypt(plainbytes,false);

            return Convert.ToBase64String(cipherbytes);
        }

        public static string DecryptData(string data2Decrypt)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            StreamReader reader = new StreamReader(path + "privatekey.xml");
            string key = reader.ReadToEnd();
            rsa.FromXmlString(key);
            reader.Close();

            byte[] plainbytes = rsa.Decrypt(Convert.FromBase64String(data2Decrypt), false);
            return System.Text.Encoding.UTF8.GetString(plainbytes);

        }


    }
}

Note however, that I'm not using a crypto container, and thus, I don't need your AssignParameter, but if you need to use it, modifying the code should be easy enough.

If you ever need to encrypt large quantities of data (much larger than 128 bytes) this article has sample code on how to do this.

like image 28
Andrew Savinykh Avatar answered Jan 28 '23 13:01

Andrew Savinykh