Пример использования OpenSSL для кодирования по алгоритму MD5
Занимаясь разработкой библиотеки для работы с протоколом SIP на языке C++ я столкнулся с проблемой кодирования в MD5. Собственно, проблему я создал сам, так как при получении нужного мне шифра я допустил хитрую ошибку в одном из преобразований, и при исполнении этого некорректного преобразования я получал правильное шифрование для одних эталонных данных и неправильное для других. Пытаясь найти ошибку я реализовал несколько вариантов разных решений, и, в конечном счете, понял досадный источник проблемы. Забавным было то, что ошибка была не в алгоритме шифрования, а в коде который превращал 128-битовую бинарную последовательность этого шифра в строку из 32-х символов. В общем, как обычно, все оказалось банальным, хотя и выглядело очень мистически.
Решив проблему я подумал, что для тех, кто впервые сталкивается с необходимостью шифрования в MD5 может показаться, что готовых рецептов в сети недостаточно и не будет лишним предложить еще один, который кажется мне наиболее простым понятным и подробным.
Надо заметить, что в своих проектах я стараюсь избегать линковки на разные малоизвестные сторонние библиотки. Особенно это справедливо для случаев, если все, что из них берется не стоит тех проблем, с которыми могут столкнуться пользователи твоего кода собирая его под другими платформами. Именно поэтому, для этого проекта я подготовил свою реализацию MD5, но в процессе поиска проблемы я, как уже упоминалось, реализовал несколько совершенно разных решений.
Думаю, что для даного случая отлично подойдет пример с использованием библиотеки Open SSL. Библиотека, как мне показалось, кроссплатформенная, и те, кто работает под Windows, смогут найти соответствующую реализацию библиотеки. В академических целях я, как и прежде, рекомендую Linux. В Linux особенно удобно программировать, так как из репозитория любого дистрибутива, обычно, легко установить нужные языки и необходимые библиотеки. То, что нам понадобится в этом примере, скорее всего, уже есть у вас по умолчанию.
Сразу поясню, что формирование шифра для диалогов SIP требует шифрования досточно хитрых исходных последовательностей в некоторые из которых могут входить MD5-шифры других. Поэтому при поиске проблемы с которой я столкнулся и о которой упомянул выше, мне надо было понять лежит ли проблема в самом шифровании, в неправильной подготовке исходных последовательностей или в выборе вариантов кодирования предлагаемых разными RFC. Вообще, шифрование для SIP/HTTP требует отдельной статьи, и, возможно, я, как-нибудь, найду время на ее написание.
Приближая пример к реальностям кодирования составных последовательностей я сразу приведу два варианта формирования шифра с внешним и внутренним накоплением данных. Под внутренним накоплением, здесь, я буду понимать накопление исходных данных шифрования внутри пространства скрытого за интерфейсом библиотеки.
Итак, приведу пример кода, который я подготовил и проверил на Linux Ubuntu 11.10 (g++ v 4.6.1).
#include <iostream> #include <openssl/md5.h> //! \brief Преобразуем бинарный шифр в строку из hex-символов std::string toHex(const char *pchData, int count) { std::string s; for(int i=0; i<count; ++i) { unsigned char ch = pchData[i]; unsigned char lo = ch%16; unsigned char hi = ch/16; s.push_back((hi<10)?(hi+0x30):(hi+87)); s.push_back((lo<10)?(lo+0x30):(lo+87)); } return s; } int main() { // Объявим контекст шифрования MD5_CTX Md5Ctx; // Результат шифрования всегда составляет 128 бит (16 байт) const int hash_length = 16; char hash[hash_length]; // Данные для исходной последовательности // Исходная последовательность должна содержать эти значения // разделенные двоеточием std::string sUserName("alice"); std::string sPassword("mielophone"); // Вариант накопления последовательности в пространстве MD5 std::string hash_string_v1; { // Передадим последовательность частями, а потом зашифруем MD5_Init(&Md5Ctx); MD5_Update(&Md5Ctx, (unsigned char *)sUserName.c_str(), sUserName.size()); MD5_Update(&Md5Ctx, (unsigned char *)":", 1); MD5_Update(&Md5Ctx, (unsigned char *)sPassword.c_str(), sPassword.size()); MD5_Final((unsigned char *)hash, &Md5Ctx); hash_string_v1 = toHex(hash, hash_length); } // Вариант внешнего накопления последовательности std::string input_string; std::string hash_string_v2; { // Накопим последовательность input_string.append(sUserName); input_string.append(":"); input_string.append(sPassword); // Передадим ее в MD5 и зашифруем MD5_Init(&Md5Ctx); MD5_Update(&Md5Ctx, (unsigned char *)input_string.c_str(), input_string.size()); MD5_Final((unsigned char *)hash, &Md5Ctx); hash_string_v2 = toHex(hash, hash_length); } // Выведем результаты шифрования одной и той же последовательности std::cout << "INPUT: " << input_string << std::endl; std::cout << "HASH_v1: " << hash_string_v1 << std::endl; std::cout << "HASH_v2: " << hash_string_v2 << std::endl; return 0; }
Для компиляции этого примера требуется подключение библиотек libcrypto.so и libexpat.so. Так как я готовил пример для системы сборки qmake, то для подключения этих библиотек мне понадобилось добавить следующую строку в файл проекта.
LIBS += -lcrypto -lexpat
После запуска получаем следующий вывод в стандартное устройство вывода.
INPUT: alice:mielophone HASH_v1: 98c322b45170287dcff3497ac2ab08ac HASH_v2: 98c322b45170287dcff3497ac2ab08ac
Для проверки такого простого случая кодирования можно воспользоваться сервисом на странице http://www.pr-cy.ru/md5 или аналогичной (введите в строку поиска Google запрос типа "шифрование md5 онлайн"). Откройте указанную или найденную по запросу страницу, введите исходную последовательность alice:mielophone и запросите вычисление хеша.
В завершении хочу обратить внимание на одну, возможно, неочевидную деталь. Если в исходные последовательности шифрования должен входить MD5-хеш вычисленный по другим последовательностям, то будьте внимательны при переводе бинарного кода хеша в строку. Необходимо обговорить регистр используемых шестнадцатеричных литер.
Комментариев нет:
Отправить комментарий