Алгоритмов хеширования достаточно много, поэтому расскажу пока о паре: SHA-256 (SHA-2) и MD5. Если Вас интересует, как реализованы алгоритмы CRC32, SHA-1 и пр. – отправляю в википедию или можете смело качать исходники по декодированию архивов, типа WinRAR (
UnRAR source code) и пр. Всё есть, надо только желание.
Некоторое время назад, в группе VO Фил МакГиннесс (Phil McGuinness) из Sherlock Software (Австралия) выложил
полный комплект для реализации алгоритма SHA-256. Я позволил себе чуток его скорректировать (под КаВо 2.8), и изменённую версию кода вы можете скачать в конце этой заметки.
В принципе, сам Фил (по наводке друзей по переписке) исходный код взял с сайта «
Frez Systems Limited». Там есть ещё много других полезных алгоритмов (например, генерация ключей, распаковка ZIP-архивов), а так же – условия пользования. После этого, он его адаптировал для КаВо и выложил для общего обозрения. И за это ему – большое спасибо!
Какое шифрование стоит использовать – Вам решать. Я придерживаюсь старой поговорки, красочно озвученной в кинофильме «Формула любви»: «Что один человек собрал, другой завсегда разобрать сможет». Поэтому, я не доверяю стойкостям алгоритма, надёжности компьютерам, а исхожу из необходимости и практичности. А практика говорят, что пусть алгоритм MD5 ломается, но он более распространён, чем SHA-2, разрешён в коммерции, а главное – более быстр. Поэтому, для проверки целостности файлов – он более чем допустим.
Писать код? «Ха-ха» три раза. Всё уже давно за нас сделано. В WinXP и более старших версиях есть прекрасные API-функции (см. cryptdll.dll, advapi32.dll). В качестве основы взял CryptDLL.dll. Прочитав
нужную статью на MSDN от Microsoft, написал структуру и три процедуры:
STRUCT MD5_CTX
MEMBER DIM state[ 2 ] AS DWORD
MEMBER DIM count[ 4 ] AS DWORD
MEMBER DIM Buffer[ 64 ] AS BYTE // Буфер
MEMBER DIM digest[ 16 ] AS BYTE // Данные из буфера после MD5Final()
_DLL PROC MD5Init( context AS MD5_CTX ) PASCAL:CryptDll.MD5Init
_DLL PROC MD5Update( context AS MD5_CTX, seq AS BYTE PTR, inputLen AS INT ) PASCAL:CryptDll.MD5Update
_DLL PROC MD5Final( context AS MD5_CTX ) PASCAL:CryptDll.MD5Final
Вот и всё! Ну и как же теперь использовать это «счастье»? А всё очень просто, и не просто-просто, а совсем просто. :)
FUNCTION GetMD5( hBuffer AS BYTE PTR, wSize AS DWORD, wBlock := 0 AS DWORD ) AS STRING PASCAL
// Description: Функция получения MD5-хэша указаной строки
// Parameters :
// hBuffer - указатель на строку, для которой формируем MD5-хэш
// wSize - размер этой строки
// wBlock - размер читаемых блоков. Если он равен нулю, то формируем MD5-хэш сразу,
// иначе - работаем блоками, указанного размера. Значение по умолчанию - 0.
// Returns : строка MD5-хэша
LOCAL cRet AS STRING
LOCAL strMD5_CTX IS MD5_CTX
LOCAL wPos AS DWORD
LOCAL wRest AS DWORD
cRet := NULL_STRING
// Готовим структуру контекста
MemSet( @strMD5_CTX, 0, _SIZEOF( MD5_CTX ) )
// Инициализируем контекст
MD5Init( @strMD5_CTX )
IF PTR( _CAST, @strMD5_CTX ) == NULL_PTR
ErrorBox{, "Ошибка !"}:Show()
RETURN cRet
ENDIF
// Формируем контекст
IF wBlock = 0 .OR. wBlock > wSize
// Формируем сразу и полностью
MD5Update( @strMD5_CTX, @hBuffer[ 1 ], INT( _CAST, wSize ) )
ELSE
// Обновляем контекст, добавляя к нему блоки данных
wRest := wSize
FOR wPos := 1 UPTO wSize STEP wBlock
IF wRest >= wBlock
wRest -= wBlock
MD5Update( @strMD5_CTX, @hBuffer[ wPos ], INT( _CAST, wBlock ) )
ELSE
MD5Update( @strMD5_CTX, @hBuffer[ wPos ], INT( _CAST, wRest ) )
ENDIF
NEXT wPos
ENDIF
// Финализируем контекст. Теперь он содержит результат
MD5Final( @strMD5_CTX )
// Получаем строковое представление MD5-хеша
FOR wPos := 1 UPTO 16
cRet += IntToHex( strMD5_CTX.digest[ wPos ], 2 ) // ANSI: 255 => FF (2 буквы)
NEXT wPos
RETURN cRet
И, тогда код в программе будет выглядеть так:
hText := String2Psz( "Hello World !" )
cMD5 := GetMD5( hText, PszLen( hText ) )
Просто замечательно! А как поступить с файлом (для контроля целостности)? Тут чуток сложнее. Файл надо весь прочесть и просчитать. И для этого я написал функцию отображения файла в память: файл открыли, сделали образ в памяти и закрыли. А потом, с образом делаем, что хотим:
FUNCTION MapFile_ReadOnly( cFileName AS STRING, dwFileSize AS USUAL ) AS BYTE PTR PASCAL
// Description: Функция создаёт и возвращает указатель на проекцию файла, и его размер.
// Как следует из названия, файл открывается только для чтения, монопольно.
// Parameters :
// Returns : Указатель на проекцию файла
LOCAL hMap AS BYTE PTR
LOCAL hFile, hFileMap AS PTR
// Открываем файл
hFile := FxOpen( cFileName, OF_READ + OF_SHARE_EXCLUSIVE, NULL_STRING )
IF ( hFile == INVALID_HANDLE_VALUE )
RETURN hMap
ENDIF
// Меряем
dwFileSize := GetFileSize( hFile, NULL_PTR )
IF dwFileSize = 0
FClose( hFile )
hFile := NULL_PTR
RETURN hMap
ENDIF
// Создаём проекцию в память для нашего файла
hFileMap := CreateFileMapping( hFile, NULL_PTR, PAGE_READONLY, 0, dwFileSize, NULL_PTR )
// Закрываем файл
FClose( hFile )
hFile := NULL_PTR
IF hFileMap == NULL_PTR .OR. GetLastError() == ERROR_ALREADY_EXISTS
RETURN hMap
ENDIF
// Отображаем проекцию в адресное пространство вызывающего процесса
hMap := MapViewOfFile( hFileMap, FILE_MAP_READ, 0, 0, dwFileSize )
CloseHandle( hFileMap )
hFileMap := NULL_PTR
RETURN hMap
Осталось проверить результат. Можно, конечно, сделать вручную,
по известному алгоритму. А можно и с помощью какой-то программки. В интернете есть много чего. Я использовал бесплатную с сайта
ImplBits. Если Вас заинтересовало моё решение – внизу заметки можно скачать полный пример работы с MD5. Всем спасибо и до новых встреч!
Жду Ваших комментариев. :)
31.08.2013 г. Карандаш.
P.S.: 06.10.2014 г.
Исправлена функция MapFile_ReadOnly(). Соответственно, загружен новый пример App_MD5_0002.rar