CryptoAPI (статья)

Обмен ключами Теперь мы располагаем набором ключей, однако все они останутся мертвым грузом, до тех пор пока мы не получим возможности обмена с другими пользователями открытыми ключами. Для этого необходимо извлечь их из базы данных ключей и записать в файл, который можно будет передать своим корреспондентам. При экспорте данные ключа сохраняются в одном из трех возможных форматов: •PUBLICKEYBLOB - используется для сохранения открытых ключей. Поскольку открытые ключи не являются секретными, они сохраняются в незашифрованном виде; •PRIVATEKEYBLOB - используется для сохранения ключевой пары целиком (открытого и закрытого ключей). Эти данные являются в высшей степени секретными, поэтому сохраняются в зашифрованном виде, причем для шифрования используется сеансовый ключ (и, соответственно, симметричный алгоритм); •SIMPLEBLOB - используется для сохранения сеансовых ключей. Для обеспечения секретности данные ключа шифруются с использованием открытого ключа получателя сообщения. Экспорт ключей в CryptoAPI выполняется функцией CryptExportKey (экспортируемый ключ, ключ адресата, формат, флаги, буфер, размер буфера): •экспортируемый ключ - дескриптор нужного ключа; •ключ адресата - в случае сохранения открытого ключа должен быть равен нулю (данные не шифруются); •формат - указывается один из возможных форматов экспорта (PUBLICKEYBLOB, PRIVATEKEYBLOB, SIMPLEBLOB); •флаги - зарезервирован на будущее (должен быть равен нулю); •буфер - содержит адрес буфера, в который будет записан ключевой BLOB (Binary Large OBject - большой двоичный объект); •размер буфера - при вызове функции в этой переменной должен находиться доступный размер буфера, а по окончании работы в нее записывается количество экспортируемых данных. Если размер буфера заранее не известен, то функцию нужно вызвать с параметром буфер, равным пустому указателю, тогда размер буфера будет вычислен и занесен в переменную размер буфера.

Экспорт ключевой пары целиком, включая и закрытый ключ, может понадобиться для того, чтобы иметь возможность подписывать документы на различных компьютерах (например, дома и на работе), или для сохранения страховочной копии. В этом случае нужно создать ключ шифрования на основании пароля и передать дескриптор этого ключа в качестве второго параметра функции CryptExportKey.
Запросить у криптопровайдера дескриптор самого' экспортируемого ключа позволяет функция CryptGetUserKey (провайдер, описание ключа, дескриптор ключа). Описание ключа - это либо AT_KEYEXCHANGE, либо AT_SIGNATURE.
Экспорт асимметричных ключей во всем возможном многообразии можно осуществить при помощи формы, показанной на рис. 9.
В Листинге приведены наиболее важные фрагменты программы

procedure TExportForm.OKBtnClick(Sender: TObject);

var cont: PChar;

err: string;

hProv: HCRYPTPROV;

key, expKey: HCRYPTKEY;

pbuf: PBYTE;

buflen: DWORD;

f: file;

hash: HCRYPTHASH;

begin

{если ни один ключ не выбран - выход}

if not (KEKCheckBox.Checked or SKCheckBox.Checked) then exit;

{если нужен пароль, т.е. экспортируется ключевая пара целиком}

if PasswEdit.Enabled and (PasswEdit.Text <> Passw2Edit.Text) then

begin

MessageDlg('Ошибка при вводе пароля! Повторите ввод.', mtError, [mbOK], 0);

exit;

end;

"считываем" имя контейнера и подключаемся к криптопровайдеру

если нужен ключ шифрования - создаем его на основании пароля

{ключ обмена ключами}

if KEKCheckBox.Checked then

repeat

{получаем дескриптор ключа}

CryptGetUserKey(hProv, AT_KEYEXCHANGE, @key);

{пытаемся определить размер буфера для экспорта ключа}

if (WhatRadioGroup.ItemIndex = 0) then

CryptExportKey(key, 0, PUBLICKEYBLOB, 0, nil, @bufLen)

else CryptExportKey(key, expKey, PRIVATEKEYBLOB, 0, nil, @bufLen);

GetMem(pbuf, bufLen);

{экспортируем данные}

if (WhatRadioGroup.ItemIndex = 0) then

CryptExportKey(key, 0, PUBLICKEYBLOB, 0, pbuf, @bufLen)

else CryptExportKey(key, expKey, PRIVATEKEYBLOB, 0, pbuf, @bufLen);

{освобождаем дескриптор ключа обмена ключами

(сам ключ при этом не уничтожается)}


CryptDestroyKey(key);

SaveDialog1.Title := 'Укажите файл для сохранения ключа обмена ключами';

if SaveDialog1.Execute then

begin

AssignFile(f, SaveDialog1.FileName);

rewrite(f, 1);

BlockWrite(f, pbuf^, bufLen);

CloseFile(f);

MessageDlg('Ключ обмена ключами успешно сохранен', mtInformation, [mbOK], 0);

end;

until true; {KeyExchange}

{ключ подписи}

if SKCheckBox.Checked then

repeat

јаналогично ключу обмена ключамиј

until true; {Signature}



если создавался ключ на основании пароля - уничтожаем его,

после чего освобождаем контекст криптопровайдера



end;

Экспортированные таким образом открытые части ключей понадобятся нам для проверки подписи и расшифровки сеансового ключа.
Импорт ключевых пар во вновь созданный контейнер - это самостоятельная процедура. Необходимо запросить у пользователя название контейнера и пароль, подключиться к провайдеру, создать на основании пароля ключ, считать из файла импортируемые данные в буфер, после чего воспользоваться функцией CryptImportKey (провайдер, буфер, длина буфера, ключ для расшифровки, флаги, импортируемый ключ). Если нужно обеспечить возможность экспорта импортируемой ключевой пары впоследствии, то в параметре флаги необходимо передать значение CRYPT_EXPORTABLE; в противном случае вызов для данной ключевой пары функции CryptExportKey приведет к ошибке.

Мы уже обсуждали, что при работе с асимметричными алгоритмами важно убедиться, что открытый ключ действительно принадлежит тому, кого вы считаете его хозяином, и не был подменен злоумышленником. Простейшим способом обеспечить аутентичность ключа является побайтная сверка с оригиналом, хранящимся у его хозяина. Для этого можно просто позволить пользователю просмотреть экспортированные данные в шестнадцатеричном виде - например, открыть файл, в который был записан открытый ключ, и вывести его содержимое в окно просмотра.
Электронная цифровая подпись

Для создания электронной цифровой подписи необходимо вычислить хеш заданного файла и зашифровать этот "цифровой отпечаток сообщения" своим закрытым ключом - "подписать". Чтобы подпись впоследствии можно было проверить, необходимо указать, какой алгоритм хеширования использовался при ее создании. Поэтому подписанное сообщение должно иметь структуру, показанную на рис. 11.
Подписать вычисленный хеш в CryptoAPI позволяет функция CryptSignHash (хеш, описание ключа, комментарий, флаги, подпись, длина подписи). Вторым параметром может быть либо AT_KEYEXCHANGE, либо AT_SIGNATURE (в нашем случае логичнее использовать ключ подписи). Третий параметр в целях безопасности настоятельно рекомендуется оставлять пустым (nil). Флаги в настоящее время также не используются - на месте этого аргумента должен быть нуль. Готовую электронную подпись функция запишет в буфер, адрес которого содержится в предпоследнем параметре, последний же параметр будет содержать длину подписи в байтах.

См. процедуру, реализующую процесс подписания; результатом ее работы является файл,

procedure TSigningForm.SignBtnClick(Sender: TObject);

var cont: PChar;

err: string;

hProv: HCRYPTPROV;

key: HCRYPTKEY;

alg: ALG_ID;

hash: HCRYPTHASH;

infile, outfile: file;

size: DWORD;

buf: array [0..511] of byte;

signature: PBYTE;

begin

{проверка существования выбранного файла}

if not FileExists(DataNameEdit.Text) then

begin

MessageDlg('Неверное имя файла!', mtError, [mbOK], 0);

exit;

end;

AssignFile(infile, DataNameEdit.Text);



"считываем" имя контейнера и подключаемся к нему



case HashRadioGroup.ItemIndex of

0: alg := CALG_MD5;

1: alg := CALG_SHA;

end;

CryptCreateHash(hProv, alg, 0, 0, @hash);

SaveDialog1.Title := 'Задайте имя файла для хранения подписанных данных';

if SaveDialog1.Execute then

begin

AssignFile(outfile, SaveDialog1.FileName);

rewrite(outfile, 1);

{записываем в файл идентификатор алгоритма хеширования}

BlockWrite(outfile, alg, 4);

reset(infile, 1);

size := FileSize(infile);

{записываем размер подписываемых данных}

BlockWrite(outfile, size, 4);

{пишем сами данные и вычисляем хеш:}

while not eof(infile) do

begin

BlockRead(infile, buf, 512, size);

BlockWrite(outFile, buf, size);

CryptHashData(hash, @buf, size, 0);

end;

CloseFile(infile);

{выясняем размер подписи}

CryptSignHash(hash, AT_SIGNATURE, nil, 0, nil, @size);

{создаем подпись}

GetMem(signature, size);

CryptSignHash(hash, AT_SIGNATURE, nil, 0, signature, @size);

BlockWrite(outfile, size, 4);

BlockWrite(outfile, signature^, size);

CloseFile(outfile);

end;



уничтожаем хеш-объект и освобождаем контекст



end;

Чтобы проверить правильность подписи, получатель подписанного сообщения должен иметь файл с открытым ключом подписи отправителя. В процессе проверки подписи этот ключ импортируется внутрь криптопровайдера. Проверка выполняется функцией CryptVerifySignature (хеш, подпись, длина подписи, открытый ключ, комментарий, флаги). О последних двух аргументах можно сказать то же, что и о параметрах комментарий и флаги функции CryptSignHash, назначение же остальных должно быть понятно. Если подпись верна, функция возвращает true. Значение false в качестве результата может свидетельствовать либо о возникновении ошибки в процессе проверки, либо о том, что подпись оказалась неверной. В последнем случае функция GetLastError вернет ошибку NTE_BAD_SIGNATURE. Для примера приведем наиболее значимые фрагменты программы проверки подписи:

procedure TMainForm.VerifyItemClick(Sender: TObject);

var err: string;

hProv: HCRYPTPROV;

key: HCRYPTKEY;

alg: ALG_ID;

hash: HCRYPTHASH;

infile: file;

size, test, textsize: DWORD;

buf: PBYTE;

signature, signkey: PBYTE;

begin



получаем контекст криптопровайдера



OpenDialog1.Title := 'Укажите файл с подписанными данными';

if OpenDialog1.Execute then

begin

AssignFile(infile, OpenDialog1.FileName);

reset(infile, 1);

{считываем идентификатор алгоритма хеширования}

BlockRead(infile, alg, 4);

{считываем размер подписанных данных и сами данные}

BlockRead(infile, textsize, 4);

GetMem(buf, textsize);

BlockRead(infile, buf^, textsize, test);

if test < textsize then

begin

MessageDlg('Неверный формат файла! Процесс прерван.', mtError, [mbOK], 0);

exit;

end;

{считываем размер подписи и саму подпись}

BlockRead(infile, test, 4);

GetMem(signature, test);

BlockRead(infile, signature^, test);

CloseFile(infile);

end

else exit;



создаем хеш-объект и хешируем данные



OpenDialog1.Title := 'Укажите файл с открытым ключом подписи';

if OpenDialog1.Execute then

begin

AssignFile(infile, OpenDialog1.FileName);

reset(infile, 1);

size := FileSize(infile);

GetMem(signkey, size);

BlockRead(infile, signkey^, size);

CloseFile(infile);

end

else exit;

{импортируем открытый ключ подписи отправителя}

CryptImportKey(hProv, signkey, size, 0, 0, @key);

FreeMem(signkey, size);

{проверяем подпись}

if CryptVerifySignature(hash, signature, test, key, nil, 0) then

begin

MessageDlg('Подпись верна.', mtInformation, [mbOK], 0);

{сохраняем подписанные данные}

SaveDialog1.Title := 'Укажите имя файла для сохранения данных';

if SaveDialog1.Execute then

begin

AssignFile(infile, SaveDialog1.FileName);

rewrite(infile, 1);

BlockWrite(infile, buf^, textsize);

CloseFile(infile);

end;

end

else

begin

case int64(GetLastError) of

NTE_BAD_SIGNATURE: err := 'Подпись неверна!';

{обработка других ошибок}

else err := 'Ошибка при проверке подписи: Unknown error';

end;

MessageDlg(err, mtError, [mbOK], 0);

end;



уничтожаем хеш-объект и импортированный ключ

и освобождаем контекст криптопровайдера



end;

Часть 3
Для конфиденциального обмена информацией с корреспондентом в любой точке земного шара приходится использовать целый арсенал современных криптографических инструментов: симметричные и асимметричные алгоритмы шифрования, механизмы генерирования криптографических ключей и случайных последовательностей, специфические режимы работы шифров и пр. Продолжая тему, начатую в первой и второй частях статьи, рассмотрим реализацию этих инструментов в CryptoAPI и воспользуемся ими для шифрования файла случайным ключом.
Цифровые конверты

Асимметричные алгоритмы позволяют легко обменяться ключами шифрования по открытому каналу связи - но работают слишком медленно. Симметричные алгоритмы работают быстро - но для обмена ключами требуют наличия защищенного канала связи и, к тому же, нуждаются в частой смене ключей. Поэтому в современных криптосистемах используются сильные стороны обоих подходов. Так, для шифрования сообщения используется симметричный алгоритм со случайным ключом шифрования, действующим только в пределах одного сеанса,- сеансовым ключом. Чтобы впоследствии сообщение могло быть расшифровано, сеансовый ключ подвергается шифрованию асимметричным алгоритмом с использованием открытого ключа получателя сообщения. Зашифрованный таким образом сеансовый ключ сохраняется вместе с сообщением, образуя цифровой конверт. При необходимости цифровой конверт может содержать сеансовый ключ в нескольких экземплярах - зашифрованный открытыми ключами различных получателей
Создание сеансовых ключей
CryptoAPI позволяет генерировать сеансовые ключи случайным образом - эту работу выполняет функция CryptGenKey, о которой шла речь ранее. Однако при использовании этой возможности за пределами США и Канады приходится учитывать американские ограничения на экспорт средств "сильной криптографии". В частности, до января 2000 года был запрещен экспорт программного обеспечения для шифрования с использованием ключей длиной более 40 бит. Этим объясняется разработка Microsoft двух версий своего криптопровайдера - базовой и расширенной. Базовая версия предназначалась на экспорт и поддерживала симметричные ключи длиной 40 бит; расширенная же версия (Microsoft Enhanced Cryptographic Provider) работала с "полной" длиной ключа (128 бит). Поскольку алгоритм шифрования, как правило, требует использования ключа строго определенной длины, недостающее количество битв в урезанном "экспортном" ключе могло быть заполнено либо нулями, либо случайными данными, которые предлагалось передавать открыто.
В криптографической практике внесение в состав ключа определенной части несекретных данных, которые сменяются несколько раз в ходе обработки исходного или шифр-текста, используется для того, чтобы воспрепятствовать взлому шифра атакой "по словарю". В английской терминологии такие вставки называются salt values: их назначение - "подсолить" ключ (с учетом нашей ментальности можно перевести как "насолить" противнику). Поскольку этот термин используется и в CryptoAPI, будем употреблять его в транслитерированном виде - солт-значения.
Итак, CryptoAPI, в экспортном исполнении практически вынуждает нас использовать солт-значения, составляющие бОльшую часть ключа - 88 бит из 128-ми для симметричных алгоритмов в RC2; и RC4. Конечно, при такой эффективной длине ключа криптозащита не может считаться достаточно надежной. В реальной ситуации выход один - воспользоваться криптопровайдером, не ограничивающим длину ключа. Обладатели Windows XP могут прибегнуть к услугам расширенных версий провайдера Microsoft (Enhanced или Strong). Пользователям более старых версий Windows, по-видимому, придется воспользоваться продуктами сторонних разработчиков. Например, свои версии криптопровайдеров предлагают российская компания "Крипто-Про" и шведская "StreamSec". В Украине, насколько известно авторам, разработкой национального провайдера криптографических услуг занимается коллектив харьковских ученых под руководством профессора Горбенко, однако до широкого внедрения дело пока не дошло. Тем не менее, благодаря архитектуре CryptoAPI, прикладные программы могут разрабатываться и отлаживаться и с базовым провайдером Microsoft - так как интерфейс взаимодействия остается неизменным. Поэтому вернемся к обсуждению создания случайных сеансовых ключей.
Солт может быть сгенерирован вместе с ключом: для этого нужно в качестве флага передать функции CryptGenKey (или CryptDeriveKey) константу CRYPT_CREATE_SALT. Правда, при сохранении ключа (с помощью функции CryptExportKey) система уже не заботится о солт-значении, перекладывая ответственность на прикладную программу. Таким образом, корректная процедура создания и сохранения симметричного ключа предполагает:
1. при создании ключа функции CryptGenKey передается значение флага CRYPT_EXPORTABLE or CRYPT_CREATE_SALT;
2. с помощью функции CryptGetKeyParam с параметром KP_SALT сгенерированное солт-значение сохраняется в буфере;
3. ключ в зашифрованном виде сохраняется в буфере при помощи функции CryptExportKey, которой передается открытый ключ обмена ключами адресата;
4. зашифрованные ключевые данные сохраняются или передаются адресату вместе с экспортированным на втором шаге солт-значением.
С другой стороны, солт-значение может быть сгенерировано и отдельно от ключа. Для этого используется функция CryptGenRandom (провайдер, длина, буфер). Здесь параметр длина задает размер генерируемой случайной последовательности в байтах, а последний аргумент задает адрес буфера, в который будет записан результат. Полученное таким образом солт-значение может быть внесено в ключ с помощью функции CryptSetKeyParam (ключ, параметр, данные, флаги). Ей вторым аргументом нужно передать KP_SALT, а третьим - адрес буфера, содержащего сгенерированную последовательность. (Последний аргумент функции зарезервирован на будущее и должен быть равен нулю.)
Блочные шифры
Блочные шифры считаются более надежными, нежели поточные, поскольку каждый блок текста подвергается сложным преобразованиям. Тем не менее, одних только этих преобразований оказывается недостаточно для обеспечения должного уровня безопасности - важно, каким образом они применяются к исходному тексту в процессе шифрования.

Отправить комментарий

Проверка
Антиспам проверка
Image CAPTCHA
...