воскресенье, 4 ноября 2012 г.

Кодирование по алгоритму MD5 при использовании OpenSSL

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

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