В одном из постов про OAuth и Twitter я говорил, что с имеющейся в наличии и единственной найденной мной библиотеке Delphi для работы с OAuth есть одна проблема — проблема русских символов. То есть текст, состоящий только из ANSI-символов передается в Твитер нормально, а стоит добавить хоть один не ANSI символ и происходит сбой — сервер не распознает подпись.
Перерыв кучу информации на эту тему вроде бы нашел причину проблемы — ошибка в шифровании подписи. И вот, в поисках ошибки и родился этот незатейливый пост в котором я приведу примеры того как можно шифровать строки средствами библиотек Indy и Synapse. Так сказать, небольшое руководство для новичков по работе с такими алгоритмами как MD5, Base64 и т.д.
Следуя простой логике можно сказать, что независимо от того какую библиотеку, функции, операции и т.д. мы используем для реализации алгоритма шифрования — результат должен быть одинаков. Иначе теряется всякий смысл алгоритмов. Поэтому я и решил первым дело посмотреть одинаков ли будет результат работы с одними и теме же алгоритмами шифрования в Synapse и Indy. При работе с OAuth используются следующие алгоритмы:
1. MD5
2. Base64
3. HMAC-SHA-1
При этом иногда требуется, например, найти хэш строки в MD5 и кодировать её с помощью Base64. Начнем, пожалуй с самого простого алгоритма — MD5.
Алгоритм MD5
Досье из Википедии:
MD5 (англ. Message Digest 5) — 128-битный алгоритм хеширования, разработанный профессором Рональдом Л. Ривестом из Массачусетского технологического института в 1991 году. Предназначен для создания «отпечатков» или «дайджестов» сообщений произвольной длины. Является улучшенной в плане безопасности версией MD4. Зная MD5-образ (называемый также MD5-хеш или MD5-дайджест), невозможно восстановить входное сообщение, так как одному MD5-образу могут соответствовать разные сообщения. Используется для проверки подлинности опубликованных сообщений путём сравнения дайджеста сообщения с опубликованным. Эту операцию называют «проверка хеша» (hashcheck).
Алгоритм MD5 используется в OAuth при генерации параметра oauth_nonce. При этом полученный хэш передается серверу в HEX-формате. Реализуем алгоритм MD5 на Indy.
Алгоритм MD5 в Indy
Для работы нам понадобится модуль IdHashMessageDigest.pas. Подключаем его в uses. Для работы с MD5 в Indy предусмотрен отдельный класс TIdHashMessageDigest5, который является наследником от TIdHashMessageDigest4 (этот класс в свою очередь реализует алгоритм MD4 и является наследником от TIdHashMessageDigest и т.д. до TObject). Для того, чтобы получить MD5-хэш нам потребуется выполнить следующие операции:
.... var md5indy: TIdHashMessageDigest; hash, HEXhash, Base: string; begin Base:='Hello World'; md5indy:=TIdHashMessageDigest5.Create;//создаем экземпляр объекта hash:=StringOf(md5indy.HashString(Base));//получаем MD5-хэш HEXhash:=md5indy.HashStringAsHex(Base);//тот же хэш, но в HEX-форме end;
Здесь метод HashString принимает на входе строку, вычисляет хэш и возвращает его в виде массива байтов (TidBytes). Поэтому дополнительно мы преобразуем этот массив в строку, используя функцию StringOf.
Метод HashStringAsHex также вычисляет MD5-хэш, но в дополнение сразу же его переводит в HEX-форму. Что тут скачать? Всё просто и удобно. Посмотрим как обстоит дело в Synapse.
Алгоритм MD5 в Synapse
Для работы с алгоритмами шифрования в Synapse предусмотрен отдельный модуль — synacode.pas в котором сосредоточены всевозможные методы реализующие самые распространенные методы шифрования. Подключаем модуль и находим в нем функцию для работы с MD5. Она так и называется:
function MD5(const Value: AnsiString): AnsiString;
Что сразу бросается в глаза после использования Indy? То, что функция принимает не массив байтов, а строку в ANSI. Это обстоятельство можно рассматривать и как плюс и как недостаток. Плюс, на мой взгляд состоит в том, что не требуется предварительно переводить строку в массив байтов. Недосаток же состоит в том, что если мы попробуем «скормить» функции следующую строку:
var S:string;
begin
result:=MD5(S)
end;
То на выходе окажется неверный результат. Поэтому предварительно нам необходимо позаботиться о том, чтобы строка была именно в ANSI-кодировке. О том как конвертировать строки я уже говорил. В модуле synacode отсутствует функция, возвращающая MD5-хэш в HEX-форме, поэтому придётся немного поработать самим (либо воспользоваться модулем synautil.pas в котором есть функция StrToHex).
Итак, получение MD5-хэшей в Synapse можно представить следующим образом:
var hex,md5hash, Base:string; Bytes: TBytes; ch: byte; begin Base:='Hello World'; md5hash:=MD5(Utf8ToAnsi(Hello World)); hex:=''; Bytes:=BytesOf(md5hash); for ch in Bytes do hex:=hex+IntToHex(ch,2); end;
Вначале мы конвертируем строку Base в кодировку Ansi и вычисляем MD5-хэш. Затем я не стал подключать лишний модуль в uses и просто перевел хэш в массив байтов и для каждый элемент массива перевел в HEX, получив на выходе из цикла ровно такую же строку, как и при использовании функции в Indy HashStringAsHex.
Алгоритм Base64
Досье из Википедии:
Base64 буквально означает — позиционная система счисления с основанием 64. Здесь 64 — это наибольшая степень двойки (26), которая может быть представлена с использованием печатных символов ASCII. Эта система широко используется в электронной почте для представления бинарных файлов в тексте письма (транспортное кодирование). Все широко известные варианты, известные под названием Base64, используют символы A-Z, a-z и 0-9, что составляет 62 знака, для остальных двух знаков в разных системах используются различные символы.
Опять же в OAuth алгоритм base64 используется перед отправкой сообщения на сервер.
Алгоритм Base64 в Indy
Для реализации Base64 в Delphi средствами Indy нам опять же требуется подключать ещё один модуль в uses — IdCoderMIME.pas и использовать класс TIdEncoderMIME — наследник от TIdEncoder, который на сей раз имеет в своем составе классовый метод:
class function EncodeBytes(const ABytes: TIdBytes): string;
Таким образом, для кодирования строки в Base64 нам необходимо:
1. Перевести строку в массив байтов;
2. Кодировать массив в Base64
Реализуется это следующим образом:
var Base:string; base64String: string; begin Base:='Hello World'; base64String:=TIdEncoderMIME.EncodeBytes(ToBytes(Base)); end;
На выходе получим строку представленную в форме Base64.
Алгоритм Base64 в Synapse
Модуль SynaCode.pas содержит следующие функции для работы с Base64:
function EncodeBase64(const Value: AnsiString): AnsiString; function DecodeBase64(const Value: AnsiString): AnsiString; function EncodeBase64mod(const Value: AnsiString): AnsiString; function DecodeBase64mod(const Value: AnsiString): AnsiString;
Последние две функции реализуют, как понятно из названия, модифицированный алгоритм кодирования Base64, который используется, например, при работе с IMAP.
Порядок кодирования строки в Base64 с использованием Synapse тот же, что и в MD5:
1. Переводим строку в кодировку ANSI
2. Передаем строку в функцию EncodeBase64.
Листинг:
var Base, Base64String: string; begin Base:='Hello World'; Base64String:=EncodeBase64(Utf8ToAnsi(Base)); end;
Алгоритм HMAC
Досье из Википедии:
HMAC (сокращение от англ. hash-based message authentication code, хеш-код идентификации сообщений) — алгоритм усиления криптостойкости других криптоалгоритмов (чаще всего MD5). Авторы — Хьюго Кравчик, Михир Беллар и Ран Каннетти.
Применительно к OAuth в Твитере HMAC используется для усиления алгоритма SHA-1:
Secure Hash Algorithm 1 — алгоритм криптографического хеширования. Описан в RFC 3174. Для входного сообщения произвольной длины (максимум 264 ? 1 бит) алгоритм генерирует 160-битное хеш-значение, называемое также дайджестом сообщения. Используется во многих криптографических приложениях и протоколах. Также рекомендован в качестве основного для государственных учреждений в США. Принципы, положенные в основу SHA-1, аналогичны тем, которые использовались Рональдом Ривестом при проектировании MD4.
Таким образом нам необходим алгоритм, реализующий вначале хэширование строки с использованием SHA-1, а затем усилить всё это дело алгоритмом HMAC. Благо, что в Indy, что в Synapse уже имеются для этого готовые решения.
HMAC-SHA1 в Indy
Опять же, следуя доброй традиции Indy, реализация HMAC-SHA1 в Delphi содержится в отдельном модуле — IdHMACSHA1.dcu. Подключаем модуль и видим, что для работы нам понадобится класс TIdHMACSHA1. Также для работы с алгоритмом нам понадобится некий ключ (key) — строка по которой и будет проходить шифрование. Реализовать алгоритм HMAC-SHA1 в Indy можно, например так (в виде отдельной функции):
function EncryptHMACSha1(Input, AKey: string): TIdBytes; begin with TIdHMACSHA1.Create do try Key := ToBytes(AKey); Result := HashValue(ToBytes(Input)); finally Free; end; end;
На выходе из функции получим массив байтов, который простой операцией:
StringOf()
можно преобразовать в строку.
HMAC-SHA1 в Synapse
В Synapse для работы с HMAC предусмотрены следующие функции:
function HMAC_MD5(Text, Key: AnsiString): AnsiString; function HMAC_SHA1(Text, Key: AnsiString): AnsiString;
Соответственно для нас сейчас необходим второй метод.
var Base, HMACString: string; begin Base:='Hello World'; HMACString:=HMAC_SHA1(Utf8ToAnsi(Base)); end;
Как видите, что в Indy, что в Synapse использовать алгоритмы шифрования достаточно просто. Не подумайте, что я ярый противник Indy, но по-моему, несмотря на то, что Synapse достаточно старая библиотека и давно не обновлялась, работать с Synapse проще. Чтобы понять как использовать Base64 в Indy мне пришлось поднимать мануалы по этой библиотеке, смотреть исходники и т.д. Ушло время. В Synapse — достаточно было открыть всего один модуль. А результат работы одинаков. Сейчас пробую переписать библиотеку OAuth под использование Synapse и дополнительно просматриваю все части кода на наличие злосчастного глюка в кодировании. Надеюсь, что задача окажется мне по силам :)
Огромное спасибо. А то даже на сайте Embarcadero поиск SHA1 и MD5 ничего не дает.
Очень полезная статья. Самое главное что реализация MD5 описана на Indy.
К стати я несовсем понял почему мы объявляем переменную типа «TIdHashMessageDigest», а не «TIdHashMessageDigest5». Я написал вот такой код и он работал:
[code]
var
res:string;
hash:TIdHashMessageDigest5;
begin
hash:=TIdHashMessageDigest5.Create;
res:=hash.AsHex(hash.HashValue(str));
end;
[/code]
Thanks!
Спасибо отличная статься но есть один вопрос можно ли в делфи реализовать аналог такого из .Net
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] Hash1 = sha.ComputeHash(StrToByteArray(richTextBox1.Text));
Проблема в том что я не могу понять какой ключ используется в .Net
В коде как видно ключа нет
Я думаю, что можно и такое реализовать. В MSDN описание класса не смотрели?
Спасибо за отличную статью!
Было бы интересно, если бы еще появился материальчик относительно обратимого шифрования. Иногда и такое нужно.
Кстати, заметил маленькую опечатку в теге статьи — стоит тег Synase а должен быть Synapse.
В статье нет информации, по как мне кажется очень важному моменту. При кодировании строки в Base64 нужно учитывать, что Base64 кодирует бинарные данные в строки, то есть прежде чем кодировать строку, строка кодируется в бинарный вид через UTF-8, Ansi, Unicode и так далее. Если я правильно понимаю функция Indy ToBytes(Base) использует по умолчанию Ansi кодировку, а в примере с Synapse используется Utf-8 кодировка.
Марат, да Вы правы — этот момент упущен. Indy где-то в своих глубинах вроде бы тоже переводит Ansi в Unicode, но наверняка сказать где это происходит сейчас не могу. Спасибо за дополнение
А не пробовали делать хеш HMAC-SHA512 в Indy& Любой другой HMAC-SHA, кроме HMAC-SHA1, при вызове HashValue вылетает в AV.
нет. такое точно не пробовал — не требовалось. Хотя и не удивляюсь, что AV может выскакивать…Indy она такая — непредсказуемая))
С AV при вызове других хешей разобрался. Но как назло проблемы с кодировкой строки хэша возникли.
Допустим HMAC-SHA1.
Если использовать StringOf для преобразования TIdBytes то получается следующие кракозябры: чнўуји•%zЅ4жвIЮ.
Преобразования в другие кодировки не помогают. Должно быть f7eda2f3bc0fe81c95257abd34e61f1ce21549de. Не подскажете где собака порылась?
Все разобрался.
Артем, поздравляю с тем что разобрались, но нельзя ли поподробнее? ;) Как именно победили кодировку?
А InDy может получить md5 если ему передать путь к файлу-картинке?
Нашёл
function GetMD5File(const fileName: string) : string;
var
idmd5 : TIdHashMessageDigest5;
fs : TFileStream;
hash : T4x4LongWordRecord;
begin
if not FileExists(fileName) then
begin
result := '';
exit;
end;
idmd5 := TIdHashMessageDigest5.Create;
fs := TFileStream.Create(fileName, fmOpenRead OR fmShareDenyWrite);
try
result := idmd5.HashStreamAsHex(fs) ;
finally
fs.Free;
idmd5.Free;
end;
end;
Как-то функция md5 на Синапс работает некорректно или я что-то делаю не так.
Я знаю, что md5(«fc2e73d4fd7dddcd31d28bea5cb2df59:/username/») должен привести к результату: 00a89418c3a4f92d5407e36116117cd9
А у меня получается:
sTempPs:=’fc2e73d4fd7dddcd31d28bea5cb2df59:/username/’;
sBlockPs:=MD5(sTempPs);
#0’Ё”’#$18’Г¤щ-T’#7’гa’#$16#$11’|Щ’
Даже близко не похоже.
Подскажите, что я делаю не так.
Переменная sTempPs, видимо, не AnsiString у вас получается, если такой результат функция выдает
Через Инди получается:
sTempPs:=’fc2e73d4fd7dddcd31d28bea5cb2df59:/username/’;
with TIdHashMessageDigest5.Create do
try
sTempPs:=AnsiLowerCase(HashStringAsHex(sTempPs));
finally
Free;
end;
а вот Синапс почему-то подводит )