Пример использования 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-хеш вычисленный по другим последовательностям, то будьте внимательны при переводе бинарного кода хеша в строку. Необходимо обговорить регистр используемых шестнадцатеричных литер.
Комментариев нет:
Отправить комментарий