Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4435.html
 

MD5-хэш средствами Delphi.

Опубликовано 21.08.2010 г. 15:30

Я тут было задумал написать клиент для работы с одним сайтом, и первое что потребовалось сделать, это сформировать md5 хэш для отправки на сервер для авторизации. Нашелся не один вариант решения данной проблемы, которые я и предлагаю здесь рассмотреть.

Поскольку просто функции md5() как в PHP в Delphi не оказалось, то я пошел изучать справку, и поиск привел меня к функции CryptCreateHash. Поскольку с первого взгляда ее описание показалось мне немного громоздким, то я отправился на MSDN поискать что-нибудь попроще, например, просто какую нибудь функцию md5() :) В таком исполнении ее не нашлось, но зато попался набор функций MD5Init, MD5Update & MD5Final. Судя по названию они решали именно ту задачу которую мне требовалось: получение хэша MD5 из строки. Давайте рассмотрим способ применения данных функций. Функция MD5Init инициализирует контекст (структуру MD5_CTX). Далее с помощью функций MD5Update в контекст передаются данные, из которых формируется хэш. После вызова MD5Final добавление новых данных становится невозможным, и в элементе digest структуры будет содержаться искомый MD5-хэш. На какое то время я призадумался, почему массив digest имеет длину 16, хотя сам хэш md5 составляет 32 символа. Ответ на данный вопрос достаточно прост, ибо 16-ти байтовый хэш записывается в строку из 16-ричных чисел, получая по 2 числа на каждый байт ($00 - $FF). Была в описании функции такая строка:
This function has no associated header file or import library. You must use the LoadLibrary or GetProcAddress functions to dynamically link to Cryptdll.dll.
Следуя данной инструкции я и начал писать исходный код, начать надо было с описания MD5_CTX, которую я назвал TMD5Context.
    TMD5Context = record
        i : array[1..2] of cardinal;
        buf : array[1..4] of cardinal;
        _in : array[1..64] of byte;
        digest : array[1..16] of byte;
    end;
Все наши функции для получения хэша будут иметь один прототип:
function MD5(const text : AnsiString):string;
Вообще на вход надо бы подавать просто массив байт, но мы выполним его в виде ANSI-строки. Теперь нам потребуется описать прототипы всех нужных функций, для этого мы заведем соответствующие переменные. Затем загрузим библиотеку cryptdll.dll и найдем нужные нам точки входа, присвоив их значения соответствующим переменным. После чего вызвав их выгрузим библиотеку за ненадобностью. Исходный код реализующий данную последовательность действий выглядит следующим образом:
function CryptDllMD5(const text:AnsiString):string;
var context : TMD5Context;
    hCrypt : HModule;
    dig : byte;

    MD5Init, MD5Final : procedure (var context : TMD5Context); stdcall;
    MD5Update         : procedure (var context : TMD5Context; const input : PAnsiChar  ; inLen: cardinal); stdcall;
begin
    hCrypt := LoadLibrary('cryptdll.dll');

    MD5Init   := getProcAddress(hCrypt, 'MD5Init');
    MD5Update := getProcAddress(hCrypt, 'MD5Update');
    MD5Final  := getProcAddress(hCrypt, 'MD5Final');

    MD5Init(context);
    MD5Update(context, pAnsiChar(text), length(text));
    MD5Final(context);

    for dig in context.digest do begin
        result := result + IntToHex(dig,2);
    end;

    FreeLibrary(hCrypt);
end;
Можно, конечно, не заморачиваться с ручной загрузкой dll и предоставить это выполнить самой delphi, только в таком случае библиотека будет загружена все время работы программы. Кстати, данные функции присутствуют как в библиотеке cryptdll.dll так и в библиотеке advapi32.dll. Итак, данный код можно изменить следующим образом, сделав его более коротким:
const advapi32 = 'advapi32.dll';

   procedure MD5Init(var context : TMD5Context);  stdcall; external advapi32 ;
   procedure MD5Final(var context : TMD5Context); stdcall; external advapi32 ;
   procedure MD5Update(var context: TMD5Context; const input : PAnsiChar; len: cardinal); stdcall; external advapi32;


function AdvApiMD5(const text:AnsiString):string;
var context : TMD5Context;
    dig : byte;
begin
    MD5Init(context);
    MD5Update(context, pAnsiChar(text), length(text));
    MD5Final(context);

    for dig in context.digest do begin
        result := result + IntToHex(dig,2);
    end;
end;
Если требуется вычислить хэш для какого либо большого объема данных, то следует разбить его на блоки, и добавлять их с помощью функции MD5Update(). Как ни странно следующий способ вычисления хэша будет самый короткий и простой в использовании. Нам потребуются компоненты Indy, с которым я кстати вообще никогда не работал. Работа будет заключаться в создании экземпляра класса TIdHashMessageDigest5.
function IndyMD5(const text:AnsiString):string;
begin
    with TIdHashMessageDigest5.Create() do begin
        result := HashStringAsHex(text);
        free();
    end;
end;
проще и короче не бывает. Вообще при всем при этом, оба варианта реализации достаточно просты. Однако если вы с помощью гугла попробуете поискать md5 + delphi, то, наверное, 99% ссылок будут вести на исходный код реализации алгоритма вычислений хэша. Кто нибудь спрашивает как вычислить хэш, а ему в ответ юнит с алгоритмом. Зачем? Зачем тратить время и писать алгоритм, зачем захламлять проекты лишними юнитами, сотнями строк кода, если все эти алгоритмы давно написаны, и могут быть использованы. Есть только один вариант, при котором требуется написать реализацию вычисления хэша самому - ты студент и твоя задача запрограммировать вычисление md5. Однако, если бы я был преподавателем, то скорей всего студенту который бы использовал API для вычисления хэша я бы поставил зачет за то, что умеет искать решения, а не изобретать велосипед. Вернемся все таки к решению задачи. Рассмотрим реализацию с помощью функции CryptCreateHash которую я упомянул в самом начале статьи. Вобще приведенные ниже функции относятся к разделу криптографии в MSDN и это действительно целый раздел, в котором описывается и работа с различными сертификатами, ключами, шифрования, вычисления хэшей, подписей и т.д. Данные функции предоставляются так называемым провайдером криптографии. Я так понимаю это служба криптографии windows, хотя мб я и не прав, поскольку читал только описание нужных мне функций и целиком тематику не изучал, да и чтобы изучить такой объем информации надо достаточно много времени. Да и прежде чем изучать как это использовать/программировать, надо знать что это вообще такое. Алгоритм работы здесь достаточно прост, изначально требуется создать "контекст" (CryptAcquireContext) для подключения к поставщику служб криптографии. При чем на этом этапе есть набор флагов (тип провадера) которые устанавливают набор поддерживаемые алгоритмов. Для наших целей подойдет практический любой тип провайдера из представленных в списке, но по описанию ближе всего подходят PROV_RSA_SIG или PROV_DSS позволяющие вычислять md5 хэши. Далее надо получить хэш-объект, с помощью которого будет проводится вычисление хэша, установив при этом тип алгоритма хеширования (CALG_MD5) Фактически это не объект в смысле класса, мы получим лишь его обработчик, который будем использовать при вызове следующих функций. Затем, потребуется передать данные, из которых мы хотим получить хэш с помощью CryptHashData() после чего можем получить обратно хэш (CryptGetHashParam()). Изначально потребуется узнать размер хэша (для разных алгоритмов размер хэша может быть различным 16 или 20 байт), хотя в принципе можем этого не делать, ибо знаем что мд5 хэш состоит из 16ти байт. Итак для начала прототипы используемых функций:
    function CryptAcquireContext( var phProv  : THandle;
                                  pszContainer : PChar;
                                  pszProvider : PChar;
                                  dwProvType, dwFlags : integer
              ) : boolean; stdcall; external advapi32 name 'CryptAcquireContextA';

    function CryptReleaseContext(hProv : THandle;
                                 flags : integer
             ):boolean; stdcall; external advapi32;

    function CryptCreateHash( hProv : THandle;
                              Algid : cardinal;
                              hKey : cardinal;
                              dwFlags : integer;
                              var phHash : THandle
             ):boolean; stdcall; external advapi32;

    function CryptDestroyHash(hHash : THandle):boolean; external advapi32 ;

    function CryptHashData(   hHash : THandle;
                              pbData : pointer;
                              dwDataLen : integer;
                              dwFlags : integer
            ):boolean; stdcall; external advapi32;

    function CryptGetHashParam(
                            hHash : THandle;
                            dwParam : integer;
                            pbData :pointer;
                            var pdwDataLen : integer;
                            dwFlags : integer
                ) : boolean; stdcall; external advapi32;
и их использование:
function CryptProviderMD5(const text:AnsiString):string;
const PROV_RSA_FULL = 1;
      PROV_DSS      = 3;
      CALG_MD5      = $00008003;
      HP_HASHSIZE   = $0004;
      HP_HASHVAL    = $0002;
      CRYPT_VERIFYCONTEXT = $F0000000;

var hProv : THandle;
    hHash : THandle;
    len : integer;
    data : array of byte;
    i : integer;
begin
    CryptAcquireContext(hProv, nil, nil, PROV_DSS,  CRYPT_VERIFYCONTEXT);
    CryptCreateHash(hProv, CALG_MD5, 0,0, hHash);

    CryptHashData(hHash, @text[1], length(text), 0);

    CryptGetHashParam(hHash, HP_HASHSIZE, @len, i, 0);
    SetLength(data, len);
    CryptGetHashParam(hHash, HP_HASHVAL, @data[0], len, 0);

    CryptDestroyHash(hHash);
    CryptReleaseContext(hProv, 0);

    for i:=0  to len - 1 do
        result := result + IntToHex(data[i], 2);
end;
Видел в гугле и достаточно дикие решения для перевода байта в строку с двумя шестнадцатеричными цифрами. Здесь не стоит наверное мучиться и достаточно воспользоваться либо функцией IntToHex(byteValue, 2) либо Format('%.2x', [byteValue]). Возможно мы еще вернемся к тематике криптографии когда нибудь (: возможно даже к цифровым подписям и их проверкам.
Метки:  cryptoApi  |  md5 

Комментарии

JayDi
21.08.2010 в 23:27
Все это уже давно есть в Delphi (в компонентах Indy):

uses IdHashMessageDigest;

function md5(SourceString: string): string;
var
md5: TIdHashMessageDigest5;
begin
// получаем md5-хэш для строки
Result := '';
md5 := TIdHashMessageDigest5.Create;
try
Result := AnsiLowerCase(md5.HashStringAsHex(SourceString));
finally
FreeAndNil(md5);
end;
end;
ter
22.08.2010 в 01:39
Если вы прочитаете статью, то заметите что это там есть.
dmitry
22.08.2010 в 09:40
Для использования криптографии в программах на delphi есть очень хорошая библиотека - DCPcrypt Cryptographic Component Library (http://www.cityinthesky.co.uk/) с открытыми исходниками и готовой реализацией большого количества алгоритмов симметричного шифрования и хеширования.
ter
27.08.2010 в 00:27
по списку алгоритмов вроде интересно (:
Dmitry
28.01.2014 в 16:44
>Есть только один вариант, при котором требуется >написать реализацию вычисления хэша самому - ты >студент и твоя задача запрограммировать вычисление >md5.

ну а если файл огромного размера?
копировать весь файл в память вычислять?
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно