Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sign a string with rsa-sha256 by using private key?

Tags:

c#

rsa

I have to sign a string with privateKey generated by OpenSSL on each API call, the privateKey is received from a database and changes for each user.

I've read that I should use RSACryptoService and add privateKey as parameter to achieve this. However I get error "Invalid data" in ImportParameters

This is the code:

string privateKey = "-----BEGIN RSA PRIVATE KEY-----
    MIIJ....."

RSAParameters rsap = new RSAParameters
{
    Modulus = Encoding.ASCII.GetBytes(privateKey)
};
rsa.ImportParameters(rsap);
byte[] encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(StringToSign), false);
string base64Encrypted = Convert.ToBase64String(encryptedData);

In the documentation of the service it is stated that it is required to sign the string with RSA-SHA256. Here is the code to sign that string in Node.JS:

const signature = crypto.createSign('RSA-SHA256').update(string).sign(privateKey, 'base64')

However, I wasn't able to find anything similar in c#.

like image 367
NiceToMytyuk Avatar asked Jul 10 '20 06:07

NiceToMytyuk


People also ask

What do I do with my RSA private key?

The RSA private key is used to generate digital signatures, and the RSA public key is used to verify digital signatures. The RSA public key is also used for key encryption of DES or AES DATA keys and the RSA private key for key recovery.

What is RSA SHA256 signature?

SHA256 with RSA signature is an efficient asymmetric encryption method used in many secure APIs. This algorithm first calculates a unique hash of the input data using SHA256 algorithm. The hash is then encrypted with a private key using the RSA algorithm.


2 Answers

In the linked answer by @SmileDeveloper How to read a PEM RSA private key from .NET there was linked the source for OpenSSLKey and by reusing some of functions linked in the source code i managed to sign my string by using my PEM privateKey as string to sign another string.

The used code to do so is the following:

    // encoding my privateKey from string to byte[] by using DecodeOpenSSLPrivateKey function from OpenSSLKey source code
         byte[] pemprivatekey = DecodeOpenSSLPrivateKey(privateKey); 

    // enconding my string to sign in byte[]
         byte[] byteSign = Encoding.ASCII.GetBytes(Sign); 

    // using DecodeRSAPrivateKey function from OpenSSLKey source code to get the RSACryptoServiceProvider with all needed parameters
          var rsa = DecodeRSAPrivateKey(pemprivatekey); 

    // Signing my string with previously get RSACryptoServiceProvider in SHA256
          var byteRSA = rsa.SignData(byteSign, CryptoConfig.MapNameToOID("SHA256"));
        
    // As required by docs converting the signed string to base64
          string Signature = Convert.ToBase64String(byteRSA);

While here are all the functions reused from OpenSSLKey used above:

    public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
    {
        byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

        // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
        MemoryStream mem = new MemoryStream(privkey);
        BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
        byte bt = 0;
        ushort twobytes = 0;
        int elems = 0;
        try
        {
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte();        //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16();       //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102) //version number
                return null;
            bt = binr.ReadByte();
            if (bt != 0x00)
                return null;


            //------  all private key components are Integer sequences ----
            elems = GetIntegerSize(binr);
            MODULUS = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            E = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            D = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            P = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            Q = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DP = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DQ = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            IQ = binr.ReadBytes(elems);

            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSAParameters RSAparams = new RSAParameters();
            RSAparams.Modulus = MODULUS;
            RSAparams.Exponent = E;
            RSAparams.D = D;
            RSAparams.P = P;
            RSAparams.Q = Q;
            RSAparams.DP = DP;
            RSAparams.DQ = DQ;
            RSAparams.InverseQ = IQ;
            RSA.ImportParameters(RSAparams);
            return RSA;
        }
        catch (Exception)
        {
            return null;
        }
        finally
        {
            binr.Close();
        }
    }

    public static byte[] DecodeOpenSSLPrivateKey(String instr)
    {
        const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
        const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
        String pemstr = instr.Trim();
        byte[] binkey;
        if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
            return null;

        StringBuilder sb = new StringBuilder(pemstr);
        sb.Replace(pemprivheader, "");  //remove headers/footers, if present
        sb.Replace(pemprivfooter, "");

        String pvkstr = sb.ToString().Trim();   //get string after removing leading/trailing whitespace

        try
        {        // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
            binkey = Convert.FromBase64String(pvkstr);
            return binkey;
        }
        catch (System.FormatException)
        {       //if can't b64 decode, it must be an encrypted private key
                //Console.WriteLine("Not an unencrypted OpenSSL PEM private key");  
        }

        StringReader str = new StringReader(pvkstr);

        //-------- read PEM encryption info. lines and extract salt -----
        if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
            return null;
        String saltline = str.ReadLine();
        if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
            return null;
        String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
        byte[] salt = new byte[saltstr.Length / 2];
        for (int i = 0; i < salt.Length; i++)
            salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
        if (!(str.ReadLine() == ""))
            return null;

        //------ remaining b64 data is encrypted RSA key ----
        String encryptedstr = str.ReadToEnd();

        try
        {   //should have b64 encrypted RSA key now
            binkey = Convert.FromBase64String(encryptedstr);
        }
        catch (System.FormatException)
        {  // bad b64 data.
            return null;
        }

        //------ Get the 3DES 24 byte key using PDK used by OpenSSL ----

        SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>");
        //Console.Write("\nEnter password to derive 3DES key: ");
        //String pswd = Console.ReadLine();
        byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2);    // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
        if (deskey == null)
            return null;
        //showBytes("3DES key", deskey) ;

        //------ Decrypt the encrypted 3des-encrypted RSA private key ------
        byte[] rsakey = DecryptKey(binkey, deskey, salt);   //OpenSSL uses salt value in PEM header also as 3DES IV
        if (rsakey != null)
            return rsakey;  //we have a decrypted RSA private key
        else
        {
            Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
            return null;
        }
    }

    private static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter)
    {
        IntPtr unmanagedPswd = IntPtr.Zero;
        int HASHLENGTH = 16;    //MD5 bytes
        byte[] keymaterial = new byte[HASHLENGTH * miter];     //to store contatenated Mi hashed results


        byte[] psbytes = new byte[secpswd.Length];
        unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
        Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
        Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);

        //UTF8Encoding utf8 = new UTF8Encoding();
        //byte[] psbytes = utf8.GetBytes(pswd);

        // --- contatenate salt and pswd bytes into fixed data array ---
        byte[] data00 = new byte[psbytes.Length + salt.Length];
        Array.Copy(psbytes, data00, psbytes.Length);        //copy the pswd bytes
        Array.Copy(salt, 0, data00, psbytes.Length, salt.Length);   //concatenate the salt bytes

        // ---- do multi-hashing and contatenate results  D1, D2 ...  into keymaterial bytes ----
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] result = null;
        byte[] hashtarget = new byte[HASHLENGTH + data00.Length];   //fixed length initial hashtarget

        for (int j = 0; j < miter; j++)
        {
            // ----  Now hash consecutively for count times ------
            if (j == 0)
                result = data00;    //initialize 
            else
            {
                Array.Copy(result, hashtarget, result.Length);
                Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
                result = hashtarget;
                //Console.WriteLine("Updated new initial hash target:") ;
                //showBytes(result) ;
            }

            for (int i = 0; i < count; i++)
                result = md5.ComputeHash(result);
            Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length);  //contatenate to keymaterial
        }
        //showBytes("Final key material", keymaterial);
        byte[] deskey = new byte[24];
        Array.Copy(keymaterial, deskey, deskey.Length);

        Array.Clear(psbytes, 0, psbytes.Length);
        Array.Clear(data00, 0, data00.Length);
        Array.Clear(result, 0, result.Length);
        Array.Clear(hashtarget, 0, hashtarget.Length);
        Array.Clear(keymaterial, 0, keymaterial.Length);

        return deskey;
    }
    public static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
    {
        MemoryStream memst = new MemoryStream();
        TripleDES alg = TripleDES.Create();
        alg.Key = desKey;
        alg.IV = IV;
        try
        {
            CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
            cs.Write(cipherData, 0, cipherData.Length);
            cs.Close();
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc.Message);
            return null;
        }
        byte[] decryptedData = memst.ToArray();
        return decryptedData;
    }
    private static SecureString GetSecPswd(String prompt)
    {
        SecureString password = new SecureString();

        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write(prompt);
        Console.ForegroundColor = ConsoleColor.Magenta;

        while (true)
        {
            ConsoleKeyInfo cki = Console.ReadKey(true);
            if (cki.Key == ConsoleKey.Enter)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine();
                return password;
            }
            else if (cki.Key == ConsoleKey.Backspace)
            {
                // remove the last asterisk from the screen...
                if (password.Length > 0)
                {
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    Console.Write(" ");
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    password.RemoveAt(password.Length - 1);
                }
            }
            else if (cki.Key == ConsoleKey.Escape)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine();
                return password;
            }
            else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
            {
                if (password.Length < 20)
                {
                    password.AppendChar(cki.KeyChar);
                    Console.Write("*");
                }
                else
                {
                    Console.Beep();
                }
            }
            else
            {
                Console.Beep();
            }
        }
    }
    private static int GetIntegerSize(BinaryReader binr)
    {
        byte bt = 0;
        byte lowbyte = 0x00;
        byte highbyte = 0x00;
        int count = 0;
        bt = binr.ReadByte();
        if (bt != 0x02)     //expect integer
            return 0;
        bt = binr.ReadByte();

        if (bt == 0x81)
            count = binr.ReadByte();    // data size in next byte
        else
        if (bt == 0x82)
        {
            highbyte = binr.ReadByte(); // data size in next 2 bytes
            lowbyte = binr.ReadByte();
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
            count = BitConverter.ToInt32(modint, 0);
        }
        else
        {
            count = bt;     // we already have the data size
        }



        while (binr.ReadByte() == 0x00)
        {   //remove high order zeros in data
            count -= 1;
        }
        binr.BaseStream.Seek(-1, SeekOrigin.Current);       //last ReadByte wasn't a removed zero, so back up a byte
        return count;
    }
like image 137
NiceToMytyuk Avatar answered Oct 21 '22 10:10

NiceToMytyuk


The problem is with this code:

RSAParameters rsap = new RSAParameters
    {
        Modulus = Encoding.ASCII.GetBytes(privateKey)
    };

You are reading privateKey as a byte array encoded in a string. But your private key is not in that format, it is in PEM format. Check this answer: How to read a PEM RSA private key from .NET. for reading a PEM key in C#. Maybe you should decrypt the base64 in your pem string aswell.

like image 25
SmileDeveloper Avatar answered Oct 21 '22 10:10

SmileDeveloper