{(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); /
(a) = ROTATE_LEFT ((a), (s)); /
(a) += (b); /
}
Сразу можно отметить, что входные параметры вышеуказанных функций представляют собой следующие значения:
register UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; Как видно регистровые переменные a, b, c, d типа UINT4 инициализируются значениями временного буфера buf[4] содержащемся в основной структуре Message Digest MD5_CTX. Пятый параметр x всегда инициализируется символом из входного буфера in[64] также находящемся в структуре MD5_CTX.
Параметр s равен одной из шестнадцати констант, определенных в функции Transform().
А вот самый последний параметр ас равен одной из 64-х "таинственных констант". Разработчики из RSA Data Security, Inc. предлагают вам расположить эти константы в "little-endian" порядке, расшифровать при помощи DES и вы получите ТАЙНЫЕ ПОСЛАНИЯ. (вот только DESовый ключик они не указали).
ROTATE_LEFT
Данный макрос сдвигает x влево на n бит.
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
Если n > 32, то результат равен нулю.
Функции
void MD5Init ( MD5_CTX *mdContext)
Функция MD5Init(MD5_CTX *mdContext) выполняет инициализацию некоторых полей структуры Message Digest MD5_CTX В качестве параметра функция получает указатель на структуру MD5_CTX.
{
/* Обнуление полей, которые будут содержать длину сообщения */
mdContext->i[0] = mdContext->i[1] = (UINT4)0;
/* Загрузка магических констант инициализации. */
mdContext->buf[0] = (UINT4)0x67452301L;
mdContext->buf[1] = (UINT4)0xefcdab89L;
mdContext->buf[2] = (UINT4)0x98badcfeL;
mdContext->buf[3] = (UINT4)0x10325476L;
}
void MD5Update (register MD5_CTX *mdContext, unsigned char *inBuf, unsigned int inLen)
Данная функция обрабатывает содержимое структуры MD5_CTX.
В качестве параметров функция получает:
Указатель mdContext на структуру MD5_CTX;
Cимвольный буфер inBuf[], который содержит символы исходного сообщения, чей Message Digest мы подсчитываем; Длину inLen передаваемого буфера.
Вначале подсчитывается целочисленная величина mdi:
mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
Эта величина равна длине сообщения в байтах по модулю 64. Длина сообщения в битах хранится в структуре MD5_CTX в буфере i[0. . 1].
Длина сообщения в битах заносится в буфер i[0. . 1] следующим образом:
mdContext->i[0] += ((UINT4)inLen << 3);
mdContext->i[1] += ((UINT4)inLen >> 29);
Следующий участок кода выполняет следующие действия:
while (inLen--)
{
/* добавляем новый символ в буфер, инкрементируя mdi */
mdContext->in[mdi++] = *inBuf++;
/* Если необходимо выполняем преобразование */
if (mdi == 0x40)
{
for (i = 0, ii = 0; i < 16; i++, ii += 4)
in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
(((UINT4)mdContext->in[ii+2]) << 16) |
(((UINT4)mdContext->in[ii+1]) << 8) |
((UINT4)mdContext->in[ii]);
Transform (mdContext->buf, in);
mdi = 0;
}
}
Пока уменьшаемая на 1 длина переданного в функцию сообщения не станет равной нулю выполняем следующие действия:
Заносим новый символ из входного буфера функции в 64-х байтный буфер структуры MD5_CTX, увеличивая при этом переменную mdi на 1. Если mdi равна 64, то преобразуем
однобайтные символы буфера in[] структуры MD5_CTX в 4-х байтные величины типа UINT4, заносим в промежуточный буфер на 16 величин типа UINT4, и далее передаем функции Transform(). Присваиваем переменной mdi 0.
void MD5Final (MD5_CTX *mdContext)
Функция завершает вычисление Message Digest и заносит полученное значение в структуре MD5_CTX в символьный буфер digest[0. . . 15].
Входным параметром функции является указатель на структуру MD5_CTX.
Основные моменты:
Расширение сообщения дополнительными заполняющими символами из таблицы PADDING[]
/* Подсчет длины сообщения в байтах по модулю 64 */
mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
/* Расширить до 56 по модулю 64 */
padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
MD5Update(mdContext, PADDING, padLen);
Присоединение битов длины и вызов функции Transform().
in[14] = mdContext->i[0];
in[15] = mdContext->i[1];
for (i = 0, ii = 0; i < 14; i++, ii += 4)
in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
(((UINT4)mdContext->in[ii+2]) << 16) |
(((UINT4)mdContext->in[ii+1]) << 8) |
((UINT4)mdContext->in[ii]);
Transform (mdContext->buf, in);
Сохранение буфера в digest(т. е. получение окончательного Message Digest):
for (i = 0, ii = 0; i < 4; i++, ii += 4)
{
mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF);
mdContext->digest[ii+1] = (unsigned char)((mdContext->buf[i] >> 8) & 0xFF);
mdContext->digest[ii+2] = (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
mdContext->digest[ii+3] = (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
}
void Transform(register UINT4 *buf, register UINT4 *in)
Данная функция является основным шагом в алгоритме MD5.
Входными параметрами являются указатель на буфер buf[] из структуры MD5_CTX и указатель на буфер in[], хранящем значения типа UINT4. Функция выполняет 4 цикла по 16 шагов в каждом. В каждом цикле используется одна из функций FF, GG, HH, II. Далее окончательный результат после 64-х преобразовательных итераций добавляется к содержимому буфера buf[].
Структура MD5_CTX
Структура MD5_CTX является основной структурой для формирования MessageDigest. Структура содержит следующие поля:
typedef struct
{
UINT4 i[2]; /* количество бит в сообщении по mod 2^64 */
UINT4 buf[4]; /* временный буфер */
unsigned char in[64]; /* входной буфер */
unsigned char digest[16]; /* содержит действительный Message Digest
после вызова MD5Final() */
} MD5_CTX;
Цифровая подпись и криптосистемы с ключом общего пользования.
Если использовать алгоритмы хэширования вместе с криптосистемами с ключом общего пользования, то можно создать цифровую подпись, гарантирующую подлинность полученного набора данных, аналогично тому, как рукописная подпись, подтверждает аутентичность напечатанного документа. Криптосистема с ключом общего пользования - это метод, позволяющий осуществлять кодирование и декодирование информации, с помощью двух исходных ключей: ключа общего пользования, свободно передаваемого всем желающим, и личного ключа, известного только его владельцу.
Смысл ключа и пароля примерно одинаков. Допустим, Том желает, чтобы Сэм мог отправить ему зашифрованный документ, и оба они не хотели бы рисковать, передавая пароль или ключ по линиям связи, так как в этом случае он может быть кем-то перехвачен. Тогда Том может передать Сэму свой ключ общего пользования (схема 1).
Используя этот ключ, Сэм шифрует документ и отправляет его Тому. Том дешифрует документ с помощью своего личного ключа. Это единственный ключ, с помощью которого можно восстановить документ, зашифрованный с применением ключа общего пользования, принадлежащего Тому. Тот факт, что ключ общего пользования Тома может стать кому-то известен, не имеет особого значения, потому что он абсолютно бесполезен для расшифровки документа. А личный ключ, известный одному лишь Тому, по открытым линиям связи не передавался; теоретически том хранит его только в собственной памяти и наоборот, работа других криптосистем с ключом общего пользования строится на обратном принципе: Сэм шифрует документ с помощью своего личного ключа и передает свой ключ общего пользования Тому, с помощью которого тот мог бы расшифровать этот документ. Существующие ныне криптосистемы с ключом общего пользования, такие, например, как система RSA (сокращение, составленное из первых букв фамилий трех создателей этого алгоритма), широко используются.
Как же осуществляется цифровая подпись? Рассмотрим еще один пример. Допустим, Сэм собирается отправить Тому контракт или номер своей кредитной карточки в цифровом виде. Для подтверждения подлинности этих документов Тому необходима цифровая подпись Сема. Сначала Сэм отправляет свой документ. Затем использует алгоритм хэширования для вычисления идентификатора этого документа, шифрует хэшированное значение с помощью своего личного ключа и отправляет его Тому. Это и есть цифровая подпись Сэма. Том с помощью того же алгоритма хэширования сначала вычисляет идентификатор принятого документа. Затем он расшифровывает значение, которое получил от Сэма, используя предоставленный Сэмом ключ общего пользования. Если два значения хэширования совпали, Том не только узнает, что этот документ подлинный, но и то, что подпись Сэма действительна. Понятно, что проведение коммерческих транзакций по такому сценарию значительно безопаснее, чем с использованием подписи от руки на бумаге, которую можно подделать. А если сведения, передаваемые Сэмом Тому, конфиденциальны (например, содержат номер кредитной карточки), то и их можно зашифровать так, чтобы прочитать их смог только Том.
Message Digest и цифровые сигнатуры
При создании цифровой сигнатуры некоторого сообщения PGP необходимо зашифровать его секретным ключом. Но PGP в действительности не использует секретный ключ для шифрования полностью всего сообщения, т. к. этот процесс был бы долгим. Вместо этого PGP шифрует MessageDigest.
MessageDigest является компактной (128 бит) "перегонкой" вашего сообщения, подобно концепции контрольной суммы. Вы также можете представить MessageDigest как "отпечаток" сообщения. MessageDigest представляет ваше сообщение таким образом, что если это сообщение будет изменено каким-либо образом, то из изменившегося сообщения будет подсчитано отличное от первоначального MessageDigest. Это дает возможность обнаружения любых изменений, сделанных взломщиком. MessageDigest вычисляется, используя криптографически устойчивую однонаправленную хэш-функцию сообщения. Подбор сообщения, которое произведет идентичное MessageDigest является с вычислительной точки зрения неосуществимо для взломщика. В этом отношении МessageDigest намного лучше, чем контрольная сумма (т. к. легче подобрать разные сообщения, которые будут иметь одинаковые контрольные суммы). Невозможно получить исходное сообщение по его MessageDigest. Одного только MessageDigest не достаточно для аутентификации сообщения. Алгоритм MD опубликован, и не требует знания секретных ключей для вычислений. Если мы просто будем прикреплять MessageDigest к сообщению, то взломщик сможет изменить сообщение, вычислить его MessageDigest, прикрепить MessageDigest к измененному сообщению и отправить дальше. Для обеспечения реальной аутентификации отправитель шифрует (подписывает) MessageDigest своим секретным ключом.
MessageDigest вычисляется из сообщения отправителем. Секретный ключ отправителя используется для зашифровки MessageDigest и некоторого временного отпечатка (timestamp), формируя цифровую сигнатуру, или сертификат сигнатуры. Отправитель посылает цифровую сигнатуру перед сообщением. Получатель получает сообщение и цифровую сигнатуру, восстанавливает исходное MessageDigest из цифровой сигнатуры при помощи расшифровки открытым ключом отправителя, заново вычисляет MessageDigest сообщения и производит сравнение с полученным от отправителя MessageDigest. Если они равны, то над сообщением не было произведено никаких изменений.