#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 КБ. Исправление заключается в изменении возвращаемого значения с achar*
на что-то, что включает длину.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 и длину; achar*
не будет.
Ответ №1:
Ваша основная проблема заключается в том, что вы работаете с неправильными типами. Шифрование работает с байтовыми массивами, а не с текстом. Вам нужно прекратить использование string
char*
и так далее. Вам нужно использовать unsigned char*
как в неуправляемом коде, так и byte[]
в управляемом коде.
Возможно, вам будет трудно принять этот совет, но я все равно предлагаю его. Помимо проблем с нулевыми байтами, которые обрабатываются как нулевые терминаторы (причина вашего усечения), ваш текущий подход просто игнорирует проблему кодирования текста. Вы не можете этого сделать.
Предположительно, у вас есть веские причины, по которым вы хотите преобразовать строку в другую строку. Это нормально, но часть шифрования преобразования должна работать с байтовыми массивами. Способ обработки шифрования текста в текст заключается в использовании цепочки преобразований. Преобразование при шифровании выполняется следующим образом:
- Преобразуйте строку в массив байтов, используя четко определенную кодировку. Например, UTF-8.
- Шифрование массива байтов, в результате чего выводится другой массив байтов.
- Закодируйте этот зашифрованный массив байтов в виде текста, используя, например, base64.
Это учитывает тот факт, что шифрование работает с байтовыми массивами, а также явно указывает на кодировку текста.
В обратном направлении вы отменяете шаги:
- Декодируйте base64 в зашифрованный байтовый массив.
- Расшифруйте этот массив байтов.
- Декодируйте расшифрованный массив байтов, используя 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. Что маршалинг на самом деле делает с этим символом *, что приводит к сбою шифрования? На самом деле, это происходит задолго до того, как маршалинг возвращается к управляемому.