Лабораторная работа «Взаимодействие с Cryptoapi. Криптопровайдеры» - umotnas.ru o_O
Главная
Поиск по ключевым словам:
страница 1
Похожие работы
Название работы Кол-во страниц Размер
«Взаимодействие с Cryptoapi. Криптопровайдеры» 1 59.4kb.
Лабораторная работа №5 Программирование под Cryptoapi теоретические... 1 753.9kb.
Лабораторная работа 4 Криптографическая защита данных с помощью microsoft... 3 446.48kb.
Лабораторная работа Лабораторная работа Основы теории множеств 7 1675.01kb.
Лабораторная работа №1 Построение детерминированного синтаксического... 1 279.02kb.
Лабораторная работа №1 Установка и настройка сетевой карты. 1 58.04kb.
Лабораторная работа №1 по курсу "Информационная безопасность" Лабораторная... 1 122.31kb.
Лабораторная работа №6 по курсу "Информационная безопасность" Лабораторная... 1 57.72kb.
Лабораторная работа по курсу Радиотехника Москва 2003 1 183.89kb.
Лабораторная работа №1 Законы сохранения в механике 2 612.89kb.
Лабораторная работа №1 по дисциплине: Дискретная математика Группа 1 77.9kb.
Тема Введение в криптологию 1 Шифрование и криптография 1 100.43kb.
Викторина для любознательных: «Занимательная биология» 1 9.92kb.

Лабораторная работа «Взаимодействие с Cryptoapi. Криптопровайдеры» - страница №1/1

Лабораторная работа 4.

«Взаимодействие с CryptoAPI. Криптопровайдеры»
Цель работы: Знакомство с CryptoAPI и криптопровайдерами.. Изучение базовых функций.
Теоретическая часть.

Введение.

Чтобы реализовать аутентификацию и обеспечить конфиденциальность информации в незащищенных сетях (например, в Интернете), требуется привлечь средства шифрования и дешифрования данных или, иными словами криптографию в качестве составной части программной системы защиты.



Криптография и CryptoAPI.

Криптография — это совокупность методов шифрования данных и сообщений для их защиты при хранении и передаче. Этот способ обеспечивает защиту соединений даже в незащищенной среде (например, при передаче информации по Интернету). Средства криптографии также позволяют зашифровывать файлы конфиденциальной информации на компьютере, чтобы взломщик не мог прочитать их содержимое.

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

Microsoft Cryptographic API (CryptoAPI) — 32-битный интерфейс прикладного программирования для Microsoft Windows, реализующий множество функций защиты информации. Поддержка CryptoAPI — составная часть Microsoft Windows NT Server, и, следовательно, эти средства доступны Internet Information Server. Если речь идет о разработке Windows-приложения для коллективной работы группы пользователей, Интернет-приложения или настольных приложений, требующих защиты, этот API идеально подходит для данных целей.

CryptoAPI находит применение в различных областях, включая:

средства проведения конференций в реальном времени;

средства передачи информации в глобальных сетях:

системы авторизации потребителей;

банковские приложения, в том числе использующие технологии смарт-карт;

утилиты шифрования и дешифрования файлов;

приложения электронной почты;

приложения коллективной работы.

CryptoAPI построен по модульному принципу. Все криптографические операции выполняют заменяемые компоненты — так называемые

поставщики криптографических услуг (Cryptographic Service Providers, CSPs). Ядром каждого CSP служит определенный криптографический алгоритм. CSP не зависит от использующего его приложения; таким образом, приложение способно работать с множеством различных CSP. Это позволяет Вам, не изменяя приложение, выбрать поставщика криптографических услуг, который обеспечит необходимый уровень защиты.



Симметричное шифрование

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



Асимметричное шифрование

Асимметричное шифрование базируется на применении двух разных ключей: открытого (public key) и личного (private key). Последний хранится у владельца пары ключей, а первый рассылается всем, кому это необходимо (как правило, с помощью цифрового сертификата). На приведенном ниже рисунке видно, что один из ключей применяется для шифрования сообщения, а второй — для его расшифровки





Цифровые подписи и цифровые конверты.

Цифровые подписи (digital signatures) и цифровые конверты (digital envelopes) создаются с использованием двух похожих процессов, однако


первый подразумевает применение личного ключа отправителя, в то время как второй — открытого ключа получателя.

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

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



Хеши.

Основное предназначение криптографических хешей - контроль подлинности данных путем вычисления от них некоторой функции Н(.), дающей результат фиксированной (и обычно небольшой длины). Функция Н(.) должна удовлетворять следующим требованиям:

Для любых сообщений m, h=H(m) легко вычисляема.

Задача нахождения такого и (отличного от m), чтобы H(u)=h, должна являться трудной при неизвестном т.

Задача нахождения такого и, что Н(u)=Н(m) является трудной при известном m.

Большинство популярных хеш-функций генерируют хеш длиной 128 бит и более. Примерами наиболее распространенных хеш-функций являются MD5 и SHA. Значения хеш-функций часто используются в системах электронной цифровой подписи для генерации дайджеста сообщения, который затем и подписывается тем или иным алгоритмом. Также хеш-функции применяются в системах аутентификации для проверки паролей - открытый пароль пользователя не должен храниться в системе, вместо него хранится его хеш, который затем и сравнивается с хешем от пароля, вводимого пользователем при входе в систему.



Обмен ключами.

Допустим, у нас есть работающий шифр (или система ЭЦП), который мы хотим использовать для защищенной передачи данных. Однако имеется одна проблема - обмен ключами. Симметричному алгоритму нужен один и тот же ключ как на шифрующей, так и на дешифрующей стороне. Требуется надежный способ генерации одинаковых сеансовых ключей на обеих сторонах или передачи ключа шифрующей стороной дешифрующей стороне (в качестве такого способа может выступать надежный курьер с дискеткой). Существуют специальные алгоритмы генерации совпадающих сеансовых ключей, но мы их рассматривать не будем, т.к. в CryptoAPI и .NET они не используются. Рассмотрим, как можно передать сгенерированный на шифрующей стороне сеансовый ключ другой стороне. Самый простой вариант - использовать шифр с открытым ключом. Имея открытый ключ получателя, мы шифруем на нем сеансовый ключ и передаем результат получателю вместе с зашифрованными данными. Теперь получатель может расшифровать сеансовый ключ и сами данные. Казалось бы, все хорошо, однако тут кроется еще одна проблема, связанная с распределением открытых ключей. В самом деле, где гарантия того, что полученный открытый ключ действительно принадлежит конкретному получателю? Если злоумышленник перехватил оригинальный открытый ключ и подсунул вместо него свой, то зашифрованный сеансовый ключ получит именно он, а не требуемый получатель. В дальнейшем злоумышленник может выдать себя за получателя и перехватить передаваемое сообщение. Для предотвращения такой возможности вводят различные сети доверия (trusted network) и авторитетные источники (trusted authority), которые берут на себя ответственность за подлинность открытого ключа (ключ подписывается собственной подписью центра, которой все безоговорочно доверяют).



Цифровые сертификаты.
Такие способы, как цифровые подписи и конверты, предполагают, что подлинность владельца открытого ключа, использованного для шифрования или дешифрования сообщения, не вызывает сомнений.

Гарантией подлинности владельцев открытых ключей служат цифровые сертификаты Microsoft Certificate Server — надежное средство для обмена открытыми ключами по незащищенным сетям.

Цифровой сертификат (digital certificate) — это совокупность данных, полностью идентифицирующих объект или субъект. Сертифицирующий орган (Certificate Authority, CA) выдает его только после того, как удостоверится, что объект или субъект является именно тем, за кого себя выдает. В состав сертификата входа открытый криптографический ключ, присвоенный владельцу сертификата. Когда отправитель сообщения подписывает его средствами своего личного ключа, получатель сообщения может использовать открытый ключ отправителя (полученный из сертификата, присланного с сообщением, или уже имеющийся в службе каталогов получателя) для проверки аутентичности отправителя.

Списки отозванных сертификатов.

Сертификаты, как и большинство применяемых в различных областях средств идентификации, могут иметь ограниченный срок действия. Но это не единственная причина, по которой "сертифицирующий орган вправе отозвать сертификат . Каждый сертифицирующий орган ведет список отозванных сертификатов (Certificate Revocation List, CRL); заглянув в него, клиенты могут проверить правомочность любого сертификата.

Если работники Вашей организации обмениваются информацией по незащищенным сетям (например, по Интернету), для аутентификации и защиты данных Вам понадобятся средства шифрования и дешифрования. Цифровые сертификаты помогают установить подлинность корреспондента и предоставляют информацию, необходимую для обеспечения конфиденциальности соединения. Цифровые подписи служат для подтверждения авторства, а цифровые конверты — для пересылки конфиденциальных сообщений, которые сможет прочитать только адресат. Открытые и личные ключи предназначены для шифрования и дешифрования сообщений; ограничение доступа к этим ключам — гарантия конфиденциальности.




Взаимодействие с CryptoAPI.

Код функций криптографической подсистемы содержится в нескольких динамически загружаемых библиотеках Windows (advapi32.dll, crypt32.dll). Для обращения к такой функции из прикладной программы на Object Pascal следует объявить ее как внешнюю. Заголовок функции в интерфейсной части модуля будет выглядеть, например, так:

function CryptAcquireContext (

phPROV: PHCRYPTPROV;

pszContainer: LPCTSTR;

pszProvider: LPCTSTR;

dwProvType: DWORD;

dwFlags: DWORD): BOOL; stdcall;

а в исполняемой части вместо тела функции нужно вписать директиву extern с указанием библиотеки, в которой содержится функция, и, возможно, ее имени в этой библиотеке (если оно отличается от имени функции в создаваемом модуле), например:

function CryptAcquireContext; external 'advapi32.dll' name 'CryptAcquireContextA';

Таким образом, имея описание функций CryptoAPI, можно собрать заголовки функций в отдельном модуле, который будет обеспечивать взаимодействие прикладной программы с криптографической подсистемой. Разумеется, такая работа была проделана программистами Microsoft, и соответствующий заголовочный файл (wincrypt.h) был включен в поставку MS Visual C++. К счастью, появилась и Delphi-версия (wcrypt2.pas). Подключив модуль к проекту, вы сможете использовать не только функции CryptoAPI, но и мнемонические константы режимов, идентификаторы алгоритмов и прочих параметров, необходимых на практике:

И последнее замечание перед тем, как опробовать CryptoAPI в деле. Ряд


функций был реализован только в Windows 2000. Но и на Windows 98 можно
найти «управу»: при установке Internet Explorer 5 интересующие нас
библиотеки обновляются, позволяя использовать новейшие криптографические
возможности. Нужно лишь задать для Delphi-проекта параметр условной
компиляции NT5, после чего вызовы функций, появившихся лишь в Windows
2000, будут нормально работать. .

Знакомство с криптопровайдерами.

Функции CryptoAPI обеспечивают прикладным программам доступ к криптографическим возможностям Windows. Однако они являются лишь «передаточным звеном» в сложной цепи обработки информации. Основную работу выполняют скрытые от глаз программиста функции, входящие в специализированные программные (или программно-аппаратные) модули — провайдеры (поставщики) криптографических услуг (CSP — Cryptographic Service Providers), или криптопровайдеры (рис. 1).

Программная часть криптопровайдера представляет собой dll-файл, подписанный Microsoft; периодически Windows проверяет цифровую подпись, что исключает возможность подмены криптопровайдера.

Криптопровайдеры отличаются друг от друга:

- составом функций (например, некоторые криптопровайдеры не выполняют шифрование

данных, ограничиваясь созданием и проверкой цифровых подписей);

- требованиями к оборудованию (специализированные криптопровайдеры могут требовать

устройства для работы со смарт-картами для выполнения аутентификации пользователя):

- алгоритмами, осуществляющими базовые действия (создание ключей, хеширование и пр.).

По составу функций и обеспечивающих их алгоритмов криптопровайдеры подразделяются на типы. Например, любой CSP типа PROV_RSA_FULL поддерживает как шифрование, так и цифровые подписи, использует для обмена ключами и создания подписей алгоритм RSA, для шифрования — алгоритмы RC2 и RC4, а для хеширования — MD5 и SHA.

В зависимости от версии операционной системы состав установленных криптопровайдеров может существенно изменяться. Однако на любом компьютере с Windows можно найти Microsoft Base Cryptographic Provider, относящийся к уже известному нам типу PROV_RSA_FULL. Именно с этим провайдером по умолчанию будут взаимодействовать все программы.

Использование криптографических возможностей Windows напоминает работу программы с графическим устройством. Криптопровайдер подобен графическому драйверу: он может обеспечивать взаимодействие программного обеспечения с оборудованием (устройство чтения смарт-карт, аппаратные датчики случайных чисел и пр.). Для вывода информации на графическое устройство приложение не должно непосредственно обращаться к драйверу — вместо этого нужно получить у системы контекст устройства, посредством которого и осуществляются все операции. Это позволяет прикладному программисту использовать графическое устройство, ничего не зная о его аппаратной реализации. Точно так же для использования криптографических функций приложение обращается к криптопровайдеру не напрямую, а через CryptoAPI. При этом вначале необходимо запросить у системы контекст криптопровайдера.

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

- CryptEnuniProviders (i, резерв, флаги, тип. имя, длина_имени) — возвращает имя и тип i-го по порядку криптопровайдера в системе (нумерация начинается с нуля);

- CryptAcquireContext (провайдер, контейнер, имя, тип, флаги) — выполняет подключение к криптопровайдеру с заданным типом и именем и возвращает его дескриптор (контекст). Приподключении мы будем передавать функции флаг CRYPT_VERIFYCONTEXT, служащий для получения контекста без подключения к контейнеру ключей;

- CryptGetProvParam (провайдер, параметр, данные, размер_данных, флаги) — возвращает значение указанного параметра провайдера, например, версии (второй параметр при вызове функции — PP_VERSION). типа реализации (программный, аппаратный, смешанный — PP_IMPTYPE). поддерживаемых алгоритмов (PP_ENUMALGS). Список поддерживаемых алгоритмов при помощи этой функции может быть получен следующим образом: при одном вызове функции возвращается информация об одном алгоритме: при первом вызове функции следует передать значение флага CRYPT_FIRST. а при последующих флаг должен быть равен 0: - CryptReleaseContext (провайдер, флаги) — освобождает дескриптор криптопровайдера.

Каждая из этих функций, как и большинство других функций CryptoAPI, возвращает логическое значение, равное true, в случае успешного завершения, и false — если возникли ошибки. Код ошибки может быть получен при помощи функции GetLastError. Возможные значения кодов ошибки приведены в упоминавшейся выше документации. Например, при вызове функции CryptGetProvParam для получения версии провайдера следует учесть возможность возникновения ошибок следующим образом:

If not CryptGetProvParam(hProv, PP_VERSION, (@vers), @DataLen, 0) Then begin

case int64(GetLastError) of

ERROR_INVALID_HANDLE: err := 'ERROR_INVALID_HANDLE'; ERROR_INVALID_PARAMETER: err :='ERROR_INVALID_PARAMETER'; ERROR_MORE_DATA err := 'ERROR_MORE_DATA'; ERROR_NO_MORE_ITEMS: err := 'ERROR_NO_MORE_ITEMS', NTE_BAD_FLAGS: err:='NTE_BAD_FLAGS'; NTE_BAD_TYPE: err := 'NTE_BAD_TYPE'; NTE_BAD_UID: err := 'NTE_BAD_UID'; else err := 'Unknown error'; end;

MessageDlg('Error of Crypt GetProvParam; ' + err, mtError. [mbOK], 0); exit end;

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

Type algInfo = record

algID: ALG_ID;

dwBits: DWORD;

dwNameLen: DWORD;

szName: array[0.. 100] of char;

end;


{вспомогательная функция, преобразующая тип провайдера в строку}

function ProvTypeToStr(provType: DWORD): string;

begin

case provType of



PROV_RSA_FULL: ProvTypeToStr := 'RSA full provider';

PROV_RSA_SIG: ProvTypeToStr := 'RSA signature provider';

PROV_DSS: ProvTypeToStr :='DSS provider'; PROV_DSS_DH: ProvTypeToStr := 'DSS and Diffie-Hellman provider'; PROV_FORTEZZA: ProvTypeToStr := 'Fortezza provider'; PROV_MS_EXCHANGE: ProvTypeToStr:= 'MS Exchange provider'; PROV_RSA_SCHANNEL: ProvTypeToStr := 'RSA secure channel provider', PROV_SSL: ProvTypeToStr:= 'SSL provider'; else ProvTypeToStr:= 'Unknown provider'; end; end;

{вспомогательная функция, преобразующая тип реализации в строку} function ImpTypeToStr(it: DWORD): string; begin case it of

CRYPT_MPL_HARDWARE: ImpTypeToStr :== 'аппаратный', CRYPT_IMPL_SOFTWARE: ImpTypeToStr := 'программный'; CRYPT_IMPL_MIXED: ImpTypeToStr := 'смешанный'; CRYPT_IMPL_UNKNOWN: ImpTypeToStr :='неизвестен'; else ImpTypeToStr:= 'неверное значение', end; end;

{процедура вывода информации о криптопровайдерах) procedure TMainForm.InfoItemClick(Sender: TObject); var i: DWORD;

dwProvType, cbName, DataLen: DWORD; provName: array[0..200] of char; vers: array[0..3] of byte; impType: DWORD; ai: alginfo; err: string; begin i:=0;

FileMemo.Clear;

while (CryptEnumProviders(i, nil, 0, {проверяем наличие еще одного} @dwProvType, nil, @cbName)) do begin

ifCryptEnumProviders(i, nil, 0, {получаем имя CSP} @dwProvType, @provName, @cbName) then begin

FileMemo.Lines.Add('Криптопровайдер: '+provName); FileMemo.Lines.Add('Тип: '+IntToStr(dwProvType)+' - '+ ProvTypeToStr(dwProvType));

If not CryptAcquireContext(@hProv, nil, provName, dwProvType, CRYPT_VERIFYCONTEXT) Then Begin

{обработка ошибок} end;

DataLen := 4;

If not CryptGetProvParam(hProv, PP_VERSION, (@vers), @DataLen, 0) Then

Begin


{обработка ошибок}

end;


FileMemo.Lines.Add('Версия:' + chr(vers[1]+) + '.'+ chr(vers[0]+));

If not CryptGetProvParam(hProv, PP_IMPTYPE, @impType, @DataLen, 0)

Then

Begin


{обработка ошибок)

end;


FileMemo.Lines.Add('Тип реализации: '+lmpTypeToStr(impType));

FileMemo.Lines.Add('Поддерживает алгоритмы:');

DataLen := sizeof(ai);

If not CryptGetProvParam(hProv, PP_ENUMALGS, @ai, @DataLen, CRYPT_FIRST)

Then

Begin


{обработка ошибок}

end;


with ai do

FileMemo.Lines.Add(szName+#9+'длина ключа - '+IntToStr(dwBits)+

' бит' +#9+ 'ID: IntToStr(AlgID));

DataLen := sizeof(ai);

while CryptGetProvParam(hProv, PP_ENUMALGS, @ai, @DataLen, 0) do

begin


with ai do FiieMemo.Lines.Add(szName+#9+'длина ключа - '

+IntToStr(dwBits)+' бит'+#9+'ID: '+IntToStr(AlgID));

DataLen := sizeof(ai);

end;


FileMemo.Lines.Add('');

CryptReleaseContext(hProv, 0);

end;

inc(i);


end;

end;




На рис. 2 показан пример отчета, выдаваемого приведенным выше кодом, выполненным в среде Windows 98.

Работа с кириптопровайдерами.

Любой сеанс работы с CryptoAPl начинается с инициализации (получения контекста). Инициализация выполняется при помощи функции CryptAcquireContext. В качестве параметров эта функция принимает имя контейнера ключей, имя криптопровайдера, тип провайдера и флаги, определяющие тип и действия с контейнером ключей и режим работы криптопровайдера:

BOOL WINAPI CryptAcquireContext(HCRYPTPROV* phProv,LPCTSTR pszContainer, LPCTSTR pszProvider,DWORD dwProvType,DWORD dwFlags);

Криптопровайдер - это сущность (обычно библиотека), реализующая определенный набор криптографических алгоритмов и обеспечивающая работу с ними. Существует около семи стандартных провайдеров, предустановленных в системе. Нам для примеров понадобятся два из них - Microsoft Base Cryptographic Provider (MS_DEF_PROV) и Microsoft Enhanced Cryptographic Provider (MS_ENHANCED_PROV).



ПРИМЕЧАНИЕ

Заметим, что Enhanced-провайдер присутствует только на тех машинах, где установлена поддержка 128-битного шифрования (она автоматически устанавливается вместе с Internet Explorer 6.0),

Каждый криптопровайдер относится к определенному типу. Это позволяет, перебрав все установленные на машине провайдеры, выбрать те, которые поддерживают нужные алгоритмы. Два упомянутых провайдера имеют тип PROV_RSA_FULL

Криптопровайдер поддерживает защищенные области, называемые контейнерами ключей. Контейнеры позволяют приложениям сохранять и использовать в дальнейшем сгенерированные один раз ключи, обеспечивая защиту самого ключа от злоумышленника.

ПРИМЕЧАНИЕ



Стандартные криптопровайдеры хранят ключи на диске, в зашифрованном виде. Однако существует потенциальная возможность, что злоумышленник, укравший компьютер или жесткий диск, сможет расшифровать сохраненные ключи.

Контейнеры бывают двух типов - пользовательские (этот тип используется по умолчанию) и машинные (CRYPT_MACHINE_KEYSET). Пользовательский контейнер доступен только приложениям, выполняемым от имени владельца контейнера. Приложение может использовать такой контейнер для сохранения персональных ключей. Доступ к машинным контейнерам разрешен только администраторам. В них обычно сохраняются ключи, используемые сервисами и системными программами. Тип контейнера задается флагом при получении контекста.



Для первоначального создания контейнера нужно вызвать CryptAcquireContext с флагом CRYPT_NEWKEYSET. Для удаления контейнера требуется указать флаг CRYPT_DELETEKEYSET.

Если приложению не требуется доступ к контейнеру ключей (например, приложение вычисляет хеш MD5), то стоит вызывать CryptAcquireContext с флагом CRYPT_VERIFYCONTEXT, передавая NULL вместо имени контейнера.

Следующий пример демонстрирует инициализацию CryptoAPI для последующего вычисления хеша MD5:

HCRYPTPROV hProv;

if(!CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL,

CRYPT_VERIFYCONTEXT)) {

MessageBoxA( "Failed to acquire cryptographic content",

"Error.", MB_OK|MB_ICONERROR);

return; }

//здесь что-то делаем //... CryptReleaseContext(hProv, 0) ;

Деинициализация CryptoAPI выполняется с помощью функции CryptReleaseContext, единственным значащим параметром которой является полученный ранее хэндл криптографического контекста.

Шифрование с использованием паролей.

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

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

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


Хешированием (от англ. hash — разрезать, крошить, перемешивать) называется преобразование строки произвольной длины в битовую последовательность фиксированной длины (хеш-значение, или просто хеш) с обеспечением следующих условий:

- по хеш-значению невозможно восстановить исходное сообщение:

- практически невозможно найти еще один текст, дающий такой же хеш. как и наперед

заданное сообщение;

- практически невозможно найти два различных текста, дающих одинаковые хеш-значения

(такие ситуации называют коллизиями).

При соблюдении приведенных условий хеш-значение служит компактным цифровым отпечатком (дайджестом) сообщения. Существует множество алгоритмов хеширования. CryptoAPI поддерживает, например, алгоритмы MD5 (MD — Message Digest) и SHA (Secure Hash Algorithm).

Итак, чтобы создать ключ шифрования на основании пароля, нам нужно вначале получить хеш этого пароля. Для этого следует создать с помощью CryptoAPI хеш-объект. воспользовавшись функцией CryptCreateHash (провайдер, ID_алгоритма, ключ, флаги, хеш), которой нужно передать дескриптор криптопровайдера (полученный с помощью CryptAcquireContext) и идентификатор алгоритма хеширования (остальные параметры могут быть нулями). В результате мы получим дескриптор хеш-объекта. Этот объект можно представить себе как черный ящик, который принимает любые данные и «перемалывает» их, сохраняя внутри себя лишь хеш-значение. Подать данные на вход хеш-объекта позволяет функция CryptHashData (дескриптор, данные, размер_данных, флаги).

Непосредственно создание ключа выполняет функция CryptDeriveKey (провайдер, ID_алгоритма, хеш-объект, флаги, ключ), которая принимает хеш-объект в качестве исходных данных и строит подходящий ключ для алгоритма шифрования, заданного своим ID. Результатом будет дескриптор ключа, который можно использовать для шифрования (рис. 3).

Следует обратить внимание, что при работе с CryptoAPI мы все время имеем дело не с самими объектами или их адресами, а с дескрипторами — целыми числами, характеризующими положение объекта во внутренних таблицах криптопровайдера. Сами таблицы располагаются в защищенной области памяти, так что программы-«шпионы» не могут получить к ним доступ.

Алгоритмы шифрования, поддерживаемые CryptoAPI, можно разделить на блочные и поточные: первые обрабатывают данные относительно большими по размеру блоками (например, 64, 128 битов или более), а вторые — побитно (теоретически, на практике же — побайтно). Если размер данных, подлежащих шифрованию, не кратен размеру блока, то последний, неполный блок данных, будет дополнен необходимым количеством случайных битов, в результате чего размер зашифрованной информации может несколько увеличиться. Разумеется, при использовании поточных шифров размер данных при шифровании остается неизменным.

Шифрование выполняется функцией CryptEncrypt (ключ, хеш, финал, флаги, данные, рамер_данных, размер_буфера):

- через параметр ключ передается дескриптор ключа шифрования:
- параметр хеш используется, если одновременно с шифрованием нужно вычислить хеш-значение шифруемого текста:

- параметр финал равен true, если шифруемый блок текста — последний или единственный (шифрование можно осуществлять частями, вызывая функцию CryptEncrypt несколько раз); - значение флага должно быть нулевым:

- параметр данные представляет собой адрес буфера, в котором при вызове функции находится исходный текст, а по завершению работы функции — зашифрованный; - следующий параметр, соответственно, описывает размер входных/выходных данных. - последний параметр задает размер буфера — если в результате шифрования зашифрованный текст не уместится в буфере, возникнет ошибка.

Для расшифровки данных используется функция CryptDecrypt (ключ, хеш, финал, флаги, данные, рамер_данных), отличающаяся от шифрующей функции только тем, что размер буфера указывать не следует: поскольку размер данных при расшифровке может только уменьшиться, отведенного под них буфера наверняка будет достаточно.

Приведем программу, реализующую шифрование.
Приложение 1.

Программный код.

// Создание цифровой подписи

// Необходимо подключить библиотеку crypt32.lib

// Компиляция с использованием Visual Studio 7.0
#include

#include // exit(1)


#include

#include


//#include

#define MY_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)


//#define _WIN32_WINNT=0x0400 // или _CRYPT32_
// Наименование персонального хранилища

#define CERT_STORE_NAME L"MY"


// Наименование сертификата установленного в это хранилище

#define SIGNER_NAME L"EVGENY"


void print_signature(DWORD cbSigned, BYTE* pbSigned);

void HandleError(char *s);


int main(int argc, char* argv[])

{

//--------------------------------------------------------------------



// Сообщение, которое мы подписываем
BYTE* pbMessage = (BYTE*)"ВАО Интурист - Туроператор N1";

DWORD cbMessage = (DWORD)strlen((char*) pbMessage)+1;


// Открываем хранилище сертификатов
HCERTSTORE hStoreHandle;
if ( !( hStoreHandle = CertOpenStore(

CERT_STORE_PROV_SYSTEM,

0,

NULL,


CERT_SYSTEM_STORE_CURRENT_USER,

//CERT_SYSTEM_STORE_LOCAL_MACHINE,

CERT_STORE_NAME)))

{

HandleError("Нельзя открыть хранилище MY.");



}
// Получаем указатель на наш сертификат
PCCERT_CONTEXT pSignerCert;
if(pSignerCert = CertFindCertificateInStore(

hStoreHandle,

MY_TYPE,

0,

CERT_FIND_SUBJECT_STR,



SIGNER_NAME,

NULL))


{

printf("Сертификат найден.\n");

}

else


{

HandleError( "Сертификат не найден.");

}
// через функцию CryptAcquireCertificatePrivateKey получаем доступ к CSP
HCRYPTPROV hProv;

DWORD dwKeySpec;

BOOL fCallerFreeProv;
if(CryptAcquireCertificatePrivateKey(

pSignerCert,

CRYPT_ACQUIRE_COMPARE_KEY_FLAG,

NULL,


&hProv,

&dwKeySpec,

&fCallerFreeProv

))

{



printf("CryptAcquireCertificatePrivateKey выполнилась успешно!\n");

}

else



{

HandleError(" Ошибка CryptAcquireCertificatePrivateKey.\n");

}
// Создаем пустой hash объект

HCRYPTHASH hHash;


if(CryptCreateHash(

hProv,


CALG_MD5,

0,


0,

&hHash))

{

printf("Hash объект создан.\n");



}

else


{

HandleError("Ошибка CryptCreateHash.");

}
// Вычисляем hash для нашего сообщения

if(CryptHashData(

hHash,

pbMessage,



cbMessage,

0))


{

printf("Hash объект вычислен.\n");

}

else


{

HandleError("Ошибка CryptHashData.");

}

// Переменные для указателя и длины подписи



BYTE *pbSignature;

DWORD dwSigLen;


if(CryptSignHash(

hHash,


dwKeySpec,

NULL,


0,

NULL,


&dwSigLen))

{

printf("Длина подписи %d .\n",dwSigLen);



}

else


{

HandleError("Ошибка CryptSignHash.");

}

if(pbSignature = new BYTE[dwSigLen])



{

printf("Память под подпись выделена.\n");

}

else


{

HandleError("Ошибка памяти.");

}
if(CryptSignHash(

hHash,


dwKeySpec,

NULL,


0,

pbSignature,

&dwSigLen))

{

printf("Подпись:\n");



print_signature(dwSigLen, pbSignature);

}

else



{

HandleError("Ошибка CryptSignHash.");

}
if(pbSignature)

delete pbSignature;


if(hHash)

CryptDestroyHash(hHash);


if(hProv)

CryptReleaseContext(hProv, 0);


if(pSignerCert)

CertFreeCertificateContext(pSignerCert);


if(CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))

{

printf("\nХранилище закрыто. \n");



}

else


{

printf("Ошибка!");

}
#include

#include

#include

// Объявление функции CryptAcquireContextEx

BOOL

CryptAcquireContextEx(



HCRYPTPROV *phProv, //out

LPTSTR pszContainer, //in

LPTSTR pszProvider, //in

DWORD dwProvType, //in

DWORD dwFlags, //in

DWORD dwKeySpec, //in

DWORD KeyFlags //in

);

void main(int argc, char *argv[]) {



// Объявление и инициализация переменных.

HCRYPTPROV hProv=0;

HCRYPTKEY hKey=0, hPubKey=0;

HCRYPTHASH hHash=0;

// Буфер данных для подписи

LPBYTE pbBuffer=(BYTE *)"The data that is to be hashed and signed.";

//Типа из командной строки

//LPBYTE pbBuffer;

LPTSTR szDescription="Test Data Description";

LPBYTE pbKeyBlob=NULL, pbSignature=NULL;

DWORD dwBufferLen = strlen((char *)pbBuffer)+1;

DWORD dwSigLen=0, dwBlobLen=0;

// *************************************************************

// Первая фаза. Подпись сообщения.

// *************************************************************

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

//if (argc!=0)pbBuffer=(BYTE *)argv[1];

//if (argc==0)pbBuffer=(BYTE *)"The data that is to be hashed and signed.";

if (!CryptAcquireContextEx(

&hProv,


NULL,

NULL,


PROV_RSA_FULL,

0,

AT_SIGNATURE,



0)) { goto ReleaseResource;

}

printf("CSP context acquired\n");



// Получение дескриптора ключа подписи.

if(!CryptGetUserKey(

hProv,

AT_SIGNATURE,



&hKey)) { printf ("Error:CryptGetUserKey=0x%X.\n",GetLastError ());

goto ReleaseResource;


}

printf("The signature key has been acquired\n");

// Для получения необходимого размера памяти параметр pbData

// равен NULL. Размер ключевого блоба возвращается в

// параметре dwBlobLen

if(!CryptExportKey( hKey, 0,

PUBLICKEYBLOB, 0,

NULL,


&dwBlobLen)) {

printf("Error:CryptExportKey=Ox%X.\n",GetLastError()); goto ReleaseResource;

}

printf("Size of the blob for the public key determined\n");



// Выделение памяти для pbKeyBlob.

pbKeyBlob=malloc (dwBlobLen);

if (!pbKeyBlob) {

printf ( "Error : Out of memory\n");

goto ReleaseResource;

}

printf ("Memory has been allocated for the blob\n");



// Экспорт открытого ключа пары подписи.

if(!CryptExportKey( hKey, 0,

PUBLICKEYBLOB, 0,

pbKeyBlob, &dwBlobLen)) {

printf("Error:CryptExportKey=Ox%X\n",GetLastError()); goto ReleaseResource;

}

printf("Contents have been written to the blob\n");



// Создание объекта хеширования.

if(!CryptCreateHash( hProv,

CALG_MD5,

0,

0,



&hHash)) {

printf("Error:CryptCreateHash=Ox%X.\n",GetLastError());

goto ReleaseResource;

}

printf("Hash object created\n");



// Хеширование буфера данных.

if (!CryptHashData(

hHash,

pbBuffer,



dwBufferLen,

0)) {


printf("Error:CryptHashData=Ox%X.\n",GetLastError());

goto ReleaseResource;

}

printf("The data buffer has been hashed\n");



// Определение необходимого размера памяти для

// буфера цифровой подписи.

if(!CryptSignHash(

hHash,


AT_SIGNATURE, szDescription,

0,

NULL,



&dwSigLen)) {

printf("Error:CryptSignHash=0x%X.\n",GetLastError());

goto ReleaseResource;

}

printf("Signature length %d found.\n",dwSigLen);



// Выделение памяти под буфер цифровой подписи.

pbSignature=malloc(dwSigLen);

if(!pbSignature) {

printf("Error: Out of memory\n");

goto ReleaseResource;

}

printf("Memory allocated for the signature\n");



// Вычисление цифровой подписи.

if(!CryptSignHash(

hHash,

AT_SIGNATURE,



szDescription,

0,

pbSignature,



&dwSigLen)) {

printf ( "Error:CryptSignHash=0x%X.\n", GetLastError () );

goto ReleaseResource;;

}

printf("pbSignature is the hash signature\n");



// Уничтожение объекта хеширования,

if (hHash) CryptDestroyHash(hHash);

hHash=0;

printf("The hash object has been destroyed\n");

printf("The signing phase of this program is completed\n\n");

// *******************************************************

// Вторая фаза. Проверка цифровой подписи сообщения. // ********************************************************

// Импортируем открытый ключ в криптопровайдер.

if(!CryptImportKey(

hProv,


pbKeyBlob,

dwBlobLen,

0,

0,

&hPubKey)) {



printf("Error:CryptImportKey=0x%X.\n",GetLastError());
goto ReleaseResource;

}

printf("The key has been imported\n");



// Создание нового объекта хеширования.

if(!CryptCreateHash(

hProv,

CALG_MD5,



0,

0,

&hHash) ) {



printf("Error:CryptCreateHash=0x%X.\n",GetLastError());

goto ReleaseResource;

}

printf ("The hash object has been recreated.\n");



// Хеширование буфера данных.

if ( !CryptHashData(

hHash,

pbBuffer,



dwBufferLen,

0)) {


printf("Error:CryptHashData=0x%X.\n",GetLastError());

goto ReleaseResource;

}

printf("The new has been created\n");



// Проверка цифровой подписи.

if (!CryptVerifySignature(

hHash,

pbSignature,



dwSigLen,

hPubKey,


szDescription,

0)) {if(GetLastError() == NTE_BAD_SIGNATURE) {

printf("SIGNATURE FAILED TO VALIDATED");

}

else {


printf ("Error:CryptVerifySignature=0x%X.\n", GetLastError () ) ;

} } else {

printf("SIGNATURE VALIDATED 0K!\n");

}

ReleaseResource:



// Освобождаем память блоба открытого ключа

if (pbKeyBlob) free(pbKeyBlob);

// Освобождаем память подписи

if (pbSignature) free(pbSignature);

// Уничтожаем объект хеширования

if (hHash) CryptDestroyHash(hHash);

// Освобождаем дескриптор кррштопровайдера

if (hProv) CryptReleaseContext(hProv, 0);

}

BOOL


CryptAcquireContextEx (

HCRYPTPROV *phProv, //out

LPTSTR pszContainer, //in

LPTSTR pszProvider, //in

DWORD dwProvType, //in

DWORD dwFlags, //in

DWORD dwKeySpec, //in

DWORD KeyFlags // in

)

{

DWORD dwError; HCRYPTKEY hKey;



// Пытаемся открыть контект криптопровайдера

if(ICryptAcquireContext( phProv, pszContainer, pszProvider,

dwProvType, dwFlags)) {

dwError=GetLastError(); if (dwError!=NTE_BAD_KEYSET) {

printf("Error:CryptAcquireContext=0x%X.\n", dwError);

return FALSE;

} else {

// Если контект не существует, создаем новый

if(!CryptAcquireContext(

phProv,


pszContainer,

pszProvider,

dwProvType,

dwFlags|CRYPT_NEWKEYSET)) {

printf("Error:CryptAcquireContext=0x%X.\n" ,

GetLastError());

return FALSE; } } }

// Если ключевая пара в контейнере отсутствует, то создаем

// новую

if (! CryptGetUserKey(*phProv,dwKeySpec,&hKey)) {

if(!CryptGenKey(*phProv,dwKeySpec, CRYPT_EXPORTABLE,&hKey)) {

CryptReleaseContext(*phProv,0);

printf ("Error:CryptGetUserKey=0x%X.\n, GetLastError () ) ;

return FALSE;

}

}

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



CryptDestroyKey(hKey);

return TRUE;

}


Контрольные вопросы.


  1. Что такое симметричное шифрование?

  2. Что значит термин асимметричное шифрование?

3.) В чём состоит различие понятий цифровые подписи и цифровые конверты?

4.) Какова основное предназначение криптографических хешей?

5.) Зачем нужны цифровые сертификаты?

6.) Какие динамические библиотеки Windows необходимы для работы с CryptoAPI?



7.) Назовите основные этапы работы с криптопровайдерами.