Проблема с управляемым дешифрованием

Я поддерживаю огромную систему C ++, первоначально разработанную в начале 1990-х годов. (Я упоминал эту систему в других своих сообщениях). Мы находимся в процессе разработки всех новых приложений C # для замены старых систем C ++. Используя TcpClient, незашифрованные или зашифрованные команды отправляются в наши приложения C # и из них в систему C ++. (шифрование - это выбираемая функция). Незашифрованные команды отправляются и принимаются без проблем. Зашифрованные команды - это отдельная история. Мы можем успешно зашифровать наши команды, и система C ++ примет эти команды. Однако зашифрованные сообщения, отправленные ИЗ C ++ В нашу систему C #, некорректно расшифровываются. Я получил несколько расшифровок, чтобы не потерпеть неудачу, но все они приводят к загадочной строке. В шифровании используются RijndaelManaged и CryptoStream, появившиеся в начале 1990-х годов. Если кто-нибудь может заставить работать код дешифрования, я бы сказал хорошее о вас на долгие годы :-) (если вам интересно, почему мы не заменяем стиль шифрования на что-то более простое, это потому, что несколько другие старые системы C ++ все еще используют эту технику)

Для отправки зашифрованной команды это работает правильно:

        //Generic messages array
        var messages = new string[4];
        messages[0] = "";
        messages[1] = "Service Manager";
        messages[2] =
            "<COMMAND_BLOCK><OPERATOR User=\"1234\" Password=\"password\"/>" +
            "<COMMAND Target=\"targetServer\" Command=\"1024\" " +
            "Data=\"1\" Priority=\"HIGH\" Id=\"-1\" " +
            "Workstation=\"Server1\"/></COMMAND_BLOCK>{Convert.ToChar(10)}";
        messages[3] = IsEncryptionEnabled ? "1" : "0"; 


                var formattedMessage = $"{messages[1]}{Convert.ToChar(10)}{messages[2]}";
                formattedMessage =
                    $"{Convert.ToChar(messages[2].Length)}{Convert.ToChar((int)Math.Floor((double)messages[2].Length / 255))}{formattedMessage}";

                if (WebApiCommon.Globals.IsEncryptionEnabled) //Yes, encryption is crazy
                {
                    var commandBytes = Encoding.UTF8.GetBytes(messages[2]);
                    var messageLength = commandBytes.Length;
                    var allocatedLength = messageLength;
                    var blockLength = 16;

                    allocatedLength = ((messageLength + blockLength - 1) / blockLength) * blockLength;

                    var messageBytes = new byte[allocatedLength];

                    for (var x = 0; x < allocatedLength; x++)
                    {
                        if (x < messageLength)
                            messageBytes[x] = commandBytes[x];
                        else // pad out for encryption
                            messageBytes[x] = 0;
                    }

                    while (keyString.Length < 16) // make sure keystring is exactly 16 bytes.
                        keyString += keyString;
                    if (keyString.Length > 16)
                        keyString = keyString.Substring(0, 16);
                    var encoding = Encoding.GetEncoding(1252); // ANSI 1252

                    var key = encoding.GetBytes(keyString);

                    //create empty IV
                    var iv = new byte[16];
                    for (var x = 0; x < iv.Length; x++)
                        iv[x] = 0;

                    //create encryption object
                    var rijndael = new RijndaelManaged
                    {
                        BlockSize = 128,
                        KeySize = 128,
                        Padding = PaddingMode.None
                    };

                    //break into blockLength byte blocks and encrypt

                    var block = new byte[blockLength];
                    var encryptedBytes = new byte[allocatedLength];
                    for (var x = 0; x < allocatedLength; x += blockLength)
                    {
                        // copy current block
                        for (var j = 0; j < blockLength; j++)
                            block[j] = messageBytes[x + j];

                        using (var ms1 = new MemoryStream())
                        {
                            using (var cs1 = new CryptoStream(ms1, rijndael.CreateEncryptor(key, iv), CryptoStreamMode.Write))
                                cs1.Write(block, 0, block.Length);
                            var block1 = ms1.ToArray();
                            for (var j = 0; j < blockLength; j++)
                                encryptedBytes[x + j] = block1[j];
                        }
                    }
                    var lsbMessageLength = new byte[2];

                    lsbMessageLength[0] = (byte)(allocatedLength & 0xFF);
                    lsbMessageLength[1] = (byte)(((allocatedLength >> 8) & 0xF) + 0x40); // 0x40 = flag to enable encryption

                    var controlPortStream = controlConnection.GetStream();
                    controlPortStream.Write(lsbMessageLength, 0, lsbMessageLength.Length);
                    controlPortStream.Write(encryptedBytes, 0, encryptedBytes.Length);
                    controlPortStream.Flush();
                }

Я включаю все свои попытки кода дешифрования, закомментированные, вместе с кодом дешифрования, который я ожидал бы сработать, но не работает. Если кто-то видит, что я делаю неправильно, дайте мне знать (желательно код):

    private string DecryptMPString(string incomingMessage)
    {
        var keyString =
            WebApiCommon.ApiRequests.SysSettings.GetList(new SysSettingDto { SectionName = "Configuration", VariableName = "Site name" }).FirstOrDefault()?.Value;

        var encryptedBytes2 = Encoding.UTF8.GetBytes(incomingMessage);
        var encryptedBytes = ConvertHexToAscii(encryptedBytes2);

        var messageLength = encryptedBytes.Length;
        var allocatedLength = messageLength;
        var blockLength = 16;

        allocatedLength = ((messageLength + blockLength - 1) / blockLength) * blockLength;

        var messageBytes = new byte[allocatedLength];

        for (var x = 0; x < allocatedLength; x++)
        {
            if (x < messageLength)
                messageBytes[x] = encryptedBytes[x];
            else // pad out for encryption
                messageBytes[x] = 0;
        }

        while (keyString.Length < 16) // make sure keystring is exactly 16 bytes.
            keyString += keyString;
        if (keyString.Length > 16)
            keyString = keyString.Substring(0, 16);
        var encoding = Encoding.GetEncoding(1252); // ANSI 1252

        var key = encoding.GetBytes(keyString);

        //create empty IV
        var iv = new byte[16];
        for (var x = 0; x < iv.Length; x++)
            iv[x] = 0;

        //create encryption object
        //var rijndael = new RijndaelManaged
        //{
        //    BlockSize = 128,
        //    KeySize = 128,
        //    Padding = PaddingMode.None
        //};

        //break into blockLength byte blocks and encrypt

        //var block = new byte[blockLength];
        //var decryptedBytes = new byte[allocatedLength];
        //for (var x = 0; x < allocatedLength; x += blockLength)
        //{
        //    // copy current block
        //    for (var j = 0; j < blockLength; j++)
        //        block[j] = messageBytes[x + j];

        using (var rijndael = new RijndaelManaged())
        {
            rijndael.BlockSize = 128;
            rijndael.KeySize = 128;
            rijndael.Padding = PaddingMode.None;

            var decryptor = rijndael.CreateDecryptor(key, iv);

            var decryptedBytes = decryptor.TransformFinalBlock(messageBytes, 0, allocatedLength);

            return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
        }

        //using (var encryptedStream = new MemoryStream(encryptedBytes))
        //    {
        //        using (var decryptedStream = new MemoryStream())
        //        {
        //            using (var cryptoStream = new CryptoStream(encryptedStream, rijndael.CreateDecryptor(key, iv), CryptoStreamMode.Read))
        //            {
        //                int data;
        //                while ((data = cryptoStream.ReadByte()) != -1)
        //                    decryptedStream.WriteByte((byte)data);
        //            }

        //            return Encoding.UTF8.GetString(decryptedStream.ToArray(), 0, decryptedStream.ToArray().Length);
        //            //var block1 = decryptedStream.ToArray();
        //        //for (var j = 0; j < blockLength; j++)
        //        //    decryptedBytes[x + j] = block1[j];
        //    }
        //}
        //}
        
        // return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);


        //using (var encryptedStream = new MemoryStream(encoding.GetBytes(incomingMessage)))
        //{
        //    //stream where decrypted contents will be stored
        //    using (var decryptedStream = new MemoryStream())
        //    {
        //        using (var aes = new RijndaelManaged { KeySize = 128, BlockSize = 128, Padding = PaddingMode.None })
        //        {
        //            using (var decryptor = aes.CreateDecryptor(key, iv))
        //            {
        //                //decrypt stream and write it to parent stream
        //                using (var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
        //                {
        //                    int data;

        //                    while ((data = cryptoStream.ReadByte()) != -1)
        //                        decryptedStream.WriteByte((byte)data);
        //                }
        //            }
        //        }

        //        //reset position in prep for reading
        //        decryptedStream.Position = 0;
        //        var decryptedBytes = decryptedStream.ToArray();
        //        return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
        //    }
        //}

    }

    private static byte[] ConvertHexToAscii(byte[] hexBytes)
    {
        int newLength = (hexBytes.Length) / 2;
        var buffer = new byte[newLength];
        for (int i = 0, j = 0; i < newLength; i++, j += 2)
            buffer[i] = (byte)(((hexBytes[j] - '0') << 4) + (hexBytes[j + 1] - '0'));

        return buffer;
    }

Это через несколько дней после первоначального вопроса, но вот код C ++ Encrypt, используемый для создания сообщения, полученного моим решением C #:

  BYTE* CEncrypt::Encrypt(LPCTSTR pszData,WORD& nLength)
  {
    if(nLength && m_bEncryptEnabled && m_nBlockSize == 16)
    {
        int nBufferSize = m_nBlockSize * (nLength/m_nBlockSize + (nLength %       m_nBlockSize == 0?0:1));
        BYTE* cBuffer = new BYTE[nBufferSize];
        if(cBuffer)
        {
            memcpy(cBuffer,pszData,nLength);
            while (nLength < nBufferSize)   // zero last bytes in case short
                    cBuffer[nLength++] = '\0';
            for (int nIndex = 0; nIndex < nLength; nIndex += m_nBlockSize)
            {
                Encrypt(cBuffer + nIndex);      
            }
        return cBuffer;
        }
    }
    return NULL;
  }

  /* There is an obvious time/space trade-off possible here.     *
   * Instead of just one ftable[], I could have 4, the other     *
   * 3 pre-rotated to save the ROTL8, ROTL16 and ROTL24 overhead */ 
  void CEncrypt::Encrypt(BYTE *buff)
  {
      int i,j,k,m;
      DWORD a[8],b[8],*x,*y,*t;

      for (i=j=0; i<Nb; i++,j+=4)
      {
          a[i] = pack((BYTE *)&buff[j]);
          a[i] ^= fkey[i];
      }
      k = Nb;
      x = a;
      y = b;

    /* State alternates between a and b */
      for (i=1; i<Nr; i++)
      { /* Nr is number of rounds. May be odd. */

        /* if Nb is fixed - unroll this next loop and hard-code in the values       of fi[]  */

          for (m=j=0; j<Nb; j++,m+=3)
          { /* deal with each 32-bit element of the State */
            /* This is the time-critical bit */
              y[j]=fkey[k++]^ftable[(BYTE)x[j]]^
                   ROTL8(ftable[(BYTE)(x[fi[m]]>>8)])^
                   ROTL16(ftable[(BYTE)(x[fi[m+1]]>>16)])^
                   ROTL24(ftable[x[fi[m+2]]>>24]);
          }
          t = x;
          x = y;
          y = t;      /* swap pointers */
      }

    /* Last Round - unroll if possible */ 
      for (m=j=0; j<Nb; j++,m+=3)
      {
          y[j] = fkey[k++]^(DWORD)fbsub[(BYTE)x[j]]^
               ROTL8((DWORD)fbsub[(BYTE)(x[fi[m]]>>8)])^
               ROTL16((DWORD)fbsub[(BYTE)(x[fi[m+1]]>>16)])^
               ROTL24((DWORD)fbsub[x[fi[m+2]]>>24]);
      }   

      for (i=j=0; i<Nb; i++,j+=4)
      {
          unpack(y[i], (BYTE *)&buff[j]);
          x[i] = y[i] = 0;   /* clean up stack */
      }
      return;
  }

person Jonathan Hansen    schedule 08.03.2021    source источник
comment
Какой именно формат incomingMessage? Кажется, вы используете его байтовый поток UTF8 в качестве зашифрованного текста, что, вероятно, неверно.   -  person John Wu    schedule 08.03.2021
comment
Я добавил текст команды вверху кода   -  person Jonathan Hansen    schedule 08.03.2021
comment
@JonathanHansen Чего-то еще не хватает - как зашифрованные байты, отправленные по сети, превращаются в string (incomingMessage)? Откуда звонят DecryptMPString()?   -  person Mathias R. Jessen    schedule 09.03.2021
comment
Спасибо за добавление текста команды, но я спросил о формате incomingMessage, который, как я полагаю, зашифрован и, следовательно, не совпадает с текстом команды в открытом виде. Это строка в кодировке base64? Это строка шестнадцатеричных цифр?   -  person John Wu    schedule 09.03.2021
comment
Ой. Данные принимаются от TcpClient следующим образом: while (_continueListening && (i = stream.Read (bytes, 0, bytes.Length))! = 0) {// Переводим байты данных в строку ASCII и добавляем их к данным нить. (эти данные передаются в метод DecryptMPString) var data = Encoding.ASCII.GetString (bytes, 0, i);   -  person Jonathan Hansen    schedule 09.03.2021
comment
Отвечая на эти вопросы, я задаюсь вопросом, как я беру байтовый массив у слушателя, конвертирую его в строку ASCII, а затем снова конвертирую в коде дешифрования в байтовый массив UTF8.   -  person Jonathan Hansen    schedule 09.03.2021
comment
Я думаю, что должно произойти string input > hex decode into byte array > decrypt > UTF8-decode bytes into a string   -  person John Wu    schedule 09.03.2021
comment
Привет, я только что добавил код C ++ к своему исходному вопросу внизу.   -  person Jonathan Hansen    schedule 10.03.2021


Ответы (2)


Проблема в том, что ваш код шифрования создает новый шифровальщик для каждого блока:

for (var x = 0; x < allocatedLength; x += blockLength)
{
    // copy current block
    for (var j = 0; j < blockLength; j++)
        block[j] = messageBytes[x + j];

    using (var ms1 = new MemoryStream())
    {
        using (var cs1 = new CryptoStream(ms1, rijndael.CreateEncryptor(key, iv), CryptoStreamMode.Write))
            cs1.Write(block, 0, block.Length);
        var block1 = ms1.ToArray();
        for (var j = 0; j < blockLength; j++)
            encryptedBytes[x + j] = block1[j];
    }
}

... что делает его по сути преобразованием ЕЦБ. Код C ++ делает то же самое.

В C # Decrypt вы пытаетесь расшифровать сразу весь блок:

using (var rijndael = new RijndaelManaged())
{
    rijndael.BlockSize = 128;
    rijndael.KeySize = 128;
    rijndael.Padding = PaddingMode.None;
    var decryptor = rijndael.CreateDecryptor(key, iv);
    var decryptedBytes = decryptor.TransformFinalBlock(messageBytes, 0, allocatedLength);
    return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
}

... который будет работать для первого блока, но не для остальных. Измените его, чтобы обрабатывать каждый блок новым дешифратором ... и он будет работать:

string finalString = "";
var blockLength = 16;
using (var rijndael = new RijndaelManaged())
{
    rijndael.BlockSize = 128;
    rijndael.KeySize = 128;
    rijndael.Padding = PaddingMode.None;
    for (var bytePos = 0; bytePos < allocatedLength; bytePos += blockLength)
    {
        var decryptor = rijndael.CreateDecryptor(key, iv);
        var decryptedBytes = decryptor.TransformFinalBlock(messageBytes.Skip(bytePos).ToArray(), 0, blockLength);
        finalString += Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
    }
}
return finalString;
person Jonathan Lawry    schedule 17.03.2021
comment
Блестяще! Это решило это! Наконец-то расшифровка работает между системой C ++ и моей системой C #! Отличная находка !! - person Jonathan Hansen; 17.03.2021

С такой проблемой обычно лучше разбить ее на наименьшие компоненты, которые вы можете. В этом случае вы должны отделить функцию шифрования и дешифрования от всей остальной логики. Это позволит вам протестировать эти функции, сравнить их вывод с известными хорошими функциями и просто выполнить код без особых усилий:

static byte[] Encrypt(string[] messages)
{
    string keyString = "mysupersecretkey";

    var formattedMessage = $"{messages[1]}{Convert.ToChar(10)}{messages[2]}";
    formattedMessage =
        $"{Convert.ToChar(messages[2].Length)}{Convert.ToChar((int)Math.Floor((double)messages[2].Length / 255))}{formattedMessage}";

    if (true /*WebApiCommon.Globals.IsEncryptionEnabled*/) //Yes, encryption is crazy
    {
        var commandBytes = Encoding.UTF8.GetBytes(formattedMessage);
        // Create a buffer block-aligned that's big enough for commandBytes
        var messageBytes = new byte[((commandBytes.Length - 1) | 15) + 1];
        // Copy commandBytes into the buffer
        Buffer.BlockCopy(commandBytes, 0, messageBytes, 0, commandBytes.Length);

        while (keyString.Length < 16) // make sure keystring is exactly 16 bytes.
            keyString += keyString;
        if (keyString.Length > 16)
            keyString = keyString.Substring(0, 16);
        var encoding = Encoding.GetEncoding(1252); // ANSI 1252

        var key = encoding.GetBytes(keyString);

        //create empty IV
        var iv = new byte[16];

        //create encryption object
        var rijndael = new RijndaelManaged
        {
            BlockSize = 128,
            KeySize = 128,
            Padding = PaddingMode.None
        };

        using (var ms1 = new MemoryStream())
        {
            // Encrypt messageBytes
            using (var cs1 = new CryptoStream(ms1, rijndael.CreateEncryptor(key, iv), CryptoStreamMode.Write))
                cs1.Write(messageBytes, 0, messageBytes.Length);
            var encryptedBytes = ms1.ToArray();

            // Create the message length
            var lsbMessageLength = new byte[2];
            lsbMessageLength[0] = (byte)(encryptedBytes.Length & 0xFF);
            lsbMessageLength[1] = (byte)(((encryptedBytes.Length >> 8) & 0xF) + 0x40); // 0x40 = flag to enable encryption

            // Combine the message length along with the encrypted bytes
            byte[] ret = new byte[lsbMessageLength.Length + encryptedBytes.Length];
            Buffer.BlockCopy(lsbMessageLength, 0, ret, 0, lsbMessageLength.Length);
            Buffer.BlockCopy(encryptedBytes, 0, ret, lsbMessageLength.Length, encryptedBytes.Length);
            // Return it
            return ret;
        }
    }
}

static string Decrypt(byte[] encryptedBytes)
{
    string keyString = "mysupersecretkey";

    // Get the message bytes from the bytes
    var allocatedLength = (encryptedBytes[1] & 0xF) << 8 | encryptedBytes[0];
    // And pull out the encrypted bytes
    var messageBytes = new byte[allocatedLength];
    Buffer.BlockCopy(encryptedBytes, 2, messageBytes, 0, allocatedLength);

    while (keyString.Length < 16) // make sure keystring is exactly 16 bytes.
        keyString += keyString;
    if (keyString.Length > 16)
        keyString = keyString.Substring(0, 16);
    var encoding = Encoding.GetEncoding(1252); // ANSI 1252

    var key = encoding.GetBytes(keyString);

    //create empty IV
    var iv = new byte[16];
    for (var x = 0; x < iv.Length; x++)
        iv[x] = 0;

    using (var rijndael = new RijndaelManaged())
    {
        rijndael.BlockSize = 128;
        rijndael.KeySize = 128;
        rijndael.Padding = PaddingMode.None;
        var decryptor = rijndael.CreateDecryptor(key, iv);
        var decryptedBytes = decryptor.TransformFinalBlock(messageBytes, 0, allocatedLength);
        return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
    }
}

Затем вы можете выполнить это с помощью нескольких быстрых вызовов:

string[] messages = { "", "system name", "Command" };
var encrypted = Encrypt(messages);
Console.WriteLine(BitConverter.ToString(encrypted).Replace("-", ""));
var decrypted = Decrypt(encrypted);
Console.WriteLine(decrypted);

Какие выходы:

2040622E097E5E8D438C9156384757A56C83B764BDF5B0D34346B2A7D56BD96016D7
 system name
Command

Затем вы можете убедиться, что зашифрованная строка соответствует реализации C ++ и что расшифрованный ответ не проходит через остальную часть сетевого кода.

person Anon Coward    schedule 08.03.2021
comment
Я сделал, как вы предложили, и создал новый проект для тестирования шифрования / дешифрования, и мой код работал нормально. Однако, поскольку зашифрованный код, поступающий из приложения C ++, по-прежнему не работает, я должен предположить, что зашифрованные байты, которые я получаю, немного отличаются. Поэтому я просто добавил код C ++ к своему исходному вопросу внизу. Возможно, это прольет свет на разницу. - person Jonathan Hansen; 10.03.2021