SomeName SomeSurname дата публикации 07-08-2007 03:18 Работаем с криптопровайдером от Крипто-Про
В данной статье я не буду пересказывать MSDN, а просто рассмотрю некоторые вопросы, с которыми столкнулся, работая с данным криптопровайдером. Как показывает практика, он достаточно распространен в России. Используется, в том числе, в системах передачи бухгалтерской и налоговой отчетности через интернет.
Как известно, шифрование в CryptoApi может осуществляться низкоуровневыми функциями, а может и высокоуровневыми (message-oriented) функциями. Я буду рассматривать низкоуровневые функции, ибо для высокоуровневых есть готовые вполне пригодные примеры из MSDN.
Пусть есть пользователь А, и пользователь Б. Оба пользователя имеют по уникальной ключевой паре. Отметим, что ключевая пара может быть либо AT_KEYEXCHANGE либо AT_SIGNATURE. Обе эти ключевые пары пригодны и для шифрования, и для подписи. Но должна быть договоренность о том, какая пара используется, ибо пары AT_KEYEXCHANGE и AT_SIGNATURE разные.
Предположим, что пользователь А желает зашифровать сообщение для Б.
Он может действовать по следующей схеме: сгенерировать сессионный ключ, зашифровать сообщение этим сессионным ключом, зашифровать сессионный ключ с помощью публичного ключа пользователя Б. Для того, чтобы Б смог расшифровать сообшение от А, ему нужны: зашифрованный сессионный ключ, вектор инициализации, публичный ключ А, и собственно сами зашифрованные данные.
Принцип зашифровки сессионного ключа таков: данные ключа шифруются с помощью ключа согласования, который может быть получен одним из двух способов: либо из публичного ключа А и ключевой пары Б, либо из публичного ключа Б и ключевой пары А.
- Получаем хэндл своей ключевой пары
- Импортируем открытый ключ получателя на свою ключевую пару
- Генерируем сессионный ключ (указываем алгоритм шифрования)
- Устанавливаем режим шифрования
- Экспортируем сессионный ключ в зашифрованном виде
- Извлекаем вектор инициализации
- Шифруем сообщение.
- Публичный ключ отправителя
- Свою ключевую пару, на публичном ключе которой шифровался сессионный ключ
- Шифрованный сессионный ключ
- Вектор инициализации
- Зашифрованные данные.
- Режим шифрования
- Идентификатор алгоритма шифрования
Код, осуществляющий шифрацию и дешифрацию данных, можно посмотреть в приложении к статье (класс TCrypter). Кроме того, в приложении есть и другие криптоориентированные классы, не рассмотренные в данной статье. Например, класс
Tcert - для работы с сертификатами (получение некоторых свойств, запись в поток, извлечение публичного ключа, получение хэндла ключевой пары, связанной с сертификатом).
Позволю себе подчеркнуть некоторые важные моменты, с которыми столкнулся при разработке класса TCrypter для работы с криптопровайдером КРИПТО-ПРО.
- Режим шифрования выставлять крайне желательно, ибо при несоответствии режимов шифрации и дешифрации при расшифровке получится не тот текст, который был зашифрован.
- При некоторых режимах шифрования размер памяти, выделенной под буфер, в котором передаются данные функции CryptEncrypt, должен быть кратен размеру блока (CryptGetKeyParam(…,KP_BLOCKSIZE,…)). Для простоты и расширяемости удобно всегда выделять память под означенный буфер кратно размеру блока.
- При каждом(!) использовании функции CryptEncrypt/CryptDecrypt меняется вектор инициализации. Это верно для Крипто-Про, для провайдеров от Микрософт такого поведения не выявлено. Поэтому при организации поблочного шифрования/дешифрования необходимо либо сохранять векторы инициализации для каждого блока, либо принудительно выставлять вектор инициализации перед шифрованием/дешифрованием каждого блока с помощью CryptSetKeyParam(..,KP_IV,..).
Список режимов шифрования можно посмотреть в файле wcrypt2.pas. Устанавливается режим так:
Var Param: Cardinal;
...
Param := CRYPT_MODE_CFB;
if not CryptSetKeyParam(hSessionKey,KP_MODE,@Param,0) then
raise TCryptException.Create(ceOtherCryptError);
Непосредственно данные не подписываются. Подписывается значение хэш-функции, вычисленное на этих данных. При проверке подписи под документом, также вычисляется хэш.
Предположим, что мы подписываем одни и те же данные несколько раз. В результате все полученные подписи будут различными! Поэтому для проверки подписи выделена специальная функция CryptVerifySignature.
Для подписи данных нужно:
- создать объект хэш ( CryptCreateHash)
- вычислить значение хэш-функции от подписываемых данных (CryptHashData)
- собственно подписать хэш (CryptSignHash)
Для проверки подписи нужно:
- создать объект хэш ( CryptCreateHash)
- вычислить значение хэш-функции от подписываемых данных (CryptHashData)
- проверить подпись (CryptVerifySignature)
Заметим, что для провайдера КРИПТО-ПРО, судя по документации, можно выставлять набор параметров с помощью CryptSetHashParam(…,HP_OID,…), передавая туда указатель на строку с параметрами, например '1.2.643.2.2.9'.
Вот список возможных вариантов параметров:
const
OID_HashTest ='1.2.643.2.2.30.0';
OID_HashVerbaO ='1.2.643.2.2.30.1';
OID_HashVar_1 ='1.2.643.2.2.30.2';
OID_HashVar_2 ='1.2.643.2.2.30.3';
OID_HashVar_3 ='1.2.643.2.2.30.4';
Пример установки параметров:
Var pch: Pchar;
…
pch := OID_HashVar_3;
if(not CryptSetHashParam(
hHash,
HP_OID,
PByte(pch),
0)) then
begin
raise TCryptException.Create(ceOtherCryptError);
end;
…
По умолчанию КРИПТО-ПРО работает с параметрами 1.2.643.2.2.30.1, так что проблемы могут возникнуть, только если с помощью CryptSetHashParam(…,HP_OID,…) установить отличные от приведенных параметры.
- Почему я использую RtlSecureZeroMemory вместо RtlZeroMemory, описано (на английском), здесь.
Саму эту функцию импортировать мне не удалось, поэтому я написал аналог.
procedure RtlSecureZeroMemory(_ptr: PVoid; size: size_t);
var i: Integer;
ptr_c: Cardinal;
begin
if IsBadWritePtr(_ptr,size) then exit;
ptr_c := Cardinal(_ptr);
for I := 0 to size - 1 do
PByte(ptr_c + i)^ := 0;
end;
- Для решения возникающих вопросов по работе с КРИПТО-ПРО существует форум техподдержки
Отвечают там не слишком скоро, зато ответы качественные.
- Исходные коды, прилагаемые к статье, находятся в процессе доработки. Но состояние их вполне рабочее.
- В ходе работы было выявлена неполнота заголовочных файлов wcrypt2.pas, некоторые не определенные там, но использовавшиеся в коде заголовки, структуры и константы определены в файле u_cryptcommon.pas, его можно посмотреть в архиве, прилагающемся к статье.
- Отдельный интерес представляет вопрос о различии между AT_KEYEXCHANGE и AT_SIGNATURE парами ключей. Причины введения именно такого разделения типов ключевых пар лично мне непонятны. Некоторые объяснения можно прочитать по ссылке (на английском)
Правда, это объяснение относится только к провайдерам от Микрософт, но я думаю, что распространить его на провайдер КРИПТО-ПРО также возможно.
-
Для запуска тестового приложения понадобится установленный провайдер КРИПТО-ПРО версии не ниже 2.0 и пара дискет) (или можно в реестр записать контейнеры).
Ins за плодотворные обсуждения, конструктивную критику материала и идею написать статью.
К материалу прилагаются файлы:
[Криптография]
Обсуждение материала [ 14-02-2020 07:27 ] 8 сообщений |