Как подписать строку с помощью rsa-sha256 с помощью закрытого ключа? - PullRequest
3 голосов
/ 10 июля 2020

Мне нужно подписать строку с privateKey, сгенерированную OpenSSL при каждом API вызове, privateKey получается из базы данных и изменяется для каждого пользователя.

Я читал, что я для этого следует использовать RSACryptoService и добавить privateKey в качестве параметра. Однако я получаю ошибку «Недопустимые данные» в ImportParameters

Это код:

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);

В документации службы указано, что необходимо подписать строку с помощью RSA-SHA256. Вот код для подписи этой строки в Node.JS:

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

Однако мне не удалось найти ничего похожего в c#.

Ответы [ 2 ]

1 голос
/ 10 июля 2020

В связанном ответе @SmileDeveloper Как прочитать закрытый ключ PEM RSA из. NET был связан источник для OpenSSLKey и путем повторного использования некоторых функций, связанных в исходном коде i удалось подписать мою строку, используя мой PEM privateKey в качестве строки для подписи другой строки.

Для этого используется следующий код:

    // 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);

А вот все функции повторно используется из OpenSSLKey, использованного выше:

    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;
    }
1 голос
/ 10 июля 2020

Проблема связана с этим кодом:

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

Вы читаете privateKey как массив байтов, закодированный в строку. Но ваш закрытый ключ не в этом формате, он в формате PEM. Отметьте этот ответ: Как прочитать закрытый ключ PEM RSA из. NET. для чтения ключа PEM в C#. Возможно, вам также следует расшифровать base64 в своей строке pem.

...