Усеченный вывод из режима CFB при вызове из C#

#c# #c #encryption #pinvoke #crypto

#c# #c #шифрование #pinvoke #крипто

Вопрос:

У меня довольно раздражающая проблема, которую я не могу решить в течение 2 дней. У меня есть encrypt() метод, который использует библиотеку Crypto , написанную на C . Метод реализован следующим образом:

 string CRijndaelHelper::Encrypt(string text)
{
    CFB_Mode< AES >::Encryption e;
    e.SetKeyWithIV(_key, sizeof(_key), _iv);

    string cipher, encoded;

    // CFB mode must not use padding. Specifying
    //  a scheme will result in an exception
    StringSource ss(text, true,
        new StreamTransformationFilter(e,
            new StringSink(cipher)
        ));     

    return cipher;
};
  

Когда я вызываю этот метод в собственной среде, он работает отлично, шифруя всю строку XML размером 26 КБ без каких-либо проблем.

В конце концов мне пришлось реализовать шифрование в коде C #, для этой цели я написал следующий код-оболочку в собственной dll для последующего использования с PInvoke:

 extern "C"
API_EXPORT char* _cdecl encrypt(char* contents)
{   
    string cont(contents);
    CRijndaelHelper rij;
    string transformedText = rij.Encrypt(cont);     
    return marshalReturn(transformedText);  
}
  

В то время как часть PInvoke выглядит следующим образом:

 [DllImport("EncryptDecrypt.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string encrypt([MarshalAs(UnmanagedType.LPStr)] string contents);
  

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

Пожалуйста, сообщите.

Комментарии:

1. Просто предположение, но rij.Encrypt(cont) , вероятно, возвращает массив с байтом 0x00 около позиции 540. Зашифрованный текст должен быть равномерно распределен по [0-255], поэтому должно быть много then в 26 КБ. Исправление заключается в изменении возвращаемого значения с a char* на что-то, что включает длину.

2. Вероятно, лучше повторно отобразить этот интерфейс API_EXPORT int _cdecl encrypt(IN unsigned char* in, OUT unsigned char* out, INOUT unsigned int* osize) .

3. Все дело в том, что rij. Encrypt() успешно получает строку xml и возвращает строку, которая правильно зашифрована при вызове из собственного кода. Как вы можете видеть из кода, CryptoPP поддерживает цель StringSink. Весь вопрос в том, что происходит с вызовом PInvoke, который влияет на API.

4. Я могу вам точно сказать, что StreamTransformationFilter(e, new StringSink(cipher)) NULL в конечном итоге в него будет встроен. С string этим все будет в порядке, поскольку строка C имеет массив символов C и длину; a char* не будет.

Ответ №1:

Ваша основная проблема заключается в том, что вы работаете с неправильными типами. Шифрование работает с байтовыми массивами, а не с текстом. Вам нужно прекратить использование string char* и так далее. Вам нужно использовать unsigned char* как в неуправляемом коде, так и byte[] в управляемом коде.

Возможно, вам будет трудно принять этот совет, но я все равно предлагаю его. Помимо проблем с нулевыми байтами, которые обрабатываются как нулевые терминаторы (причина вашего усечения), ваш текущий подход просто игнорирует проблему кодирования текста. Вы не можете этого сделать.

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

  1. Преобразуйте строку в массив байтов, используя четко определенную кодировку. Например, UTF-8.
  2. Шифрование массива байтов, в результате чего выводится другой массив байтов.
  3. Закодируйте этот зашифрованный массив байтов в виде текста, используя, например, base64.

Это учитывает тот факт, что шифрование работает с байтовыми массивами, а также явно указывает на кодировку текста.

В обратном направлении вы отменяете шаги:

  1. Декодируйте base64 в зашифрованный байтовый массив.
  2. Расшифруйте этот массив байтов.
  3. Декодируйте расшифрованный массив байтов, используя UTF-8, чтобы получить исходную строку.

Комментарии:

1. Согласен с вами, Дэвид, но, к сожалению, компания использует множество файлов, которые уже зашифрованы с помощью этого точного подхода, поэтому я боюсь, что изменение кодировки может вызвать проблемы. Код CRijndaelHelper::Encrypt() был написан задолго до того, как я начал свою реализацию. Однако описанный выше подход JWW, поддержанный вашим ответом, мне очень помог. Спасибо!

2. Принятый вами ответ предполагает изменение подхода, не сильно отличающееся от того, что я предлагаю. В другом ответе не рассматривается возможность обнаружения текста, отличного от ASCII.

3. Это именно то, что я хотел сказать.

4. @aquila — Дэвид прав насчет проблем с дизайном. Я помог только с очень узкой проблемой (встроенная NULL причина усечения). У вас все еще есть большие проблемы, которые необходимо решить. Хотя Дэвид предложил UTF-8, я бы сказал, что вы должны использовать его для переносимости.

5. @jww Спасибо за щедрый комментарий. Я бы сказал, что UTF8 — не единственный возможный выбор. Хотя, скорее всего, это лучший вариант. Иногда UTF16 может быть лучшим. Однако ключевым моментом является то, что кодировка, используемая для перехода от текста к массиву байтов (и обратно), четко определена явным образом.

Ответ №2:

… возвращает строку, которая правильно зашифрована при вызове из собственного кода

Проблема не в вашем C encrypt , который использует a std::string . Проблема заключается в том, чтобы вернуть его обратно в управляемый код как char* .

Измените CRijndaelHelper::Encrypt на следующее, чтобы удалить встроенные NULL , которые будут щедро разбрызгиваться с вероятностью 1/255:

 #include <cryptopp/base64.h>
using CryptoPP::Base64Encoder;
...

string CRijndaelHelper::Encrypt(string text)
{
    CFB_Mode< AES >::Encryption e;
    e.SetKeyWithIV(_key, sizeof(_key), _iv);

    string cipher, encoded;

    // CFB mode must not use padding. Specifying
    //  a scheme will result in an exception
    StringSource ss(text, true,
        new StreamTransformationFilter(e,
            new Base64Encoder(
                new StringSink(cipher)
        )));     

    return cipher;
};
  

При Base64 кодировании маршалинг как a char* не будет усекать результаты шифрования для первого NULL встреченного байта.

Затем для расшифровки:

 StringSource ss(cipher, true,
    new Base64Decoder(
        new StreamTransformationFilter(d,
            new StringSink(text)
    )));   
  

Комментарии:

1. Ты мужик! 🙂 Теперь работает. Я думаю, что никогда не поймал бы это без посторонней помощи. Единственное, что я изо всех сил пытаюсь понять, это в чем фактическая разница между std::string, созданным изначально, и std::string, инициализированным с помощью массива char *, маршалируемого из CLR. Что маршалинг на самом деле делает с этим символом *, что приводит к сбою шифрования? На самом деле, это происходит задолго до того, как маршалинг возвращается к управляемому.