logo
Добро пожаловать, Гость! Чтобы использовать все возможности Вход или Регистрация.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline Андрей Т.  
#1 Оставлено : 11 февраля 2019 г. 4:25:55(UTC)
Андрей Т.

Статус: Новичок

Группы: Участники
Зарегистрирован: 11.02.2019(UTC)
Сообщений: 3
Российская Федерация
Откуда: Новосибирск

Добрый день.
В компании возникла необходимость работать с ЭЦП.
Используется Delphi 2006, установлен CryptoPro 4.0, носитель ключа Рутокен.

При использовании CryptoApi все функции, работающие с ключами возвращают ошибку "нет ключа" или "ключ не существует".
Тестирование ключевого контейнера с помощью CryptoPro CSP выдает, в том числе, строку "Ключ подписи отсутствует".
Однако, регистрация ключа на сайте с использованием плагина Криптопро, для работы с ЭЦП, прошла успешно.

Вопрос. Что я делаю неправильно? Где понимаю неправильно?

И заранее прошу извинить, я еще путаюсь немного с терминологией.
Offline two_oceans  
#2 Оставлено : 11 февраля 2019 г. 9:06:09(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 266
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 15 раз
Поблагодарили: 63 раз в 61 постах
В контейнере могут присутствовать ключ подписи и ключ обмена - если вкратце: отличаются разрешенными операциями, ключу обмена разрешено больше, он может и подписывать тоже. Оба этих вида ключа могут быть в одном контейнере, но как правило при генерации ключа создается новый контейнер и поэтому (если конечно вручную не добавляли второй) в контейнере будет только один ключ - или подписи или обмена. Поэтому если есть строка ключ обмена присутствует, строка "ключ подписи отсутствует" обычное дело и не является ошибкой.

Другой вопрос что от того, какой ключ в контейнере зависит какую константу нужно использовать при обращении к ключу. Например, если ключ подписи нужно ставить AT_SIGNATURE, если ключ обмена AT_KEYEXCHANGE. По описанному "ключ подписи отсутствует" похоже, что у Вас ключ обмена, тогда если указана константа AT_SIGNATURE, в этом случае криптопровайдер скажет, что в данном контейнере нет ключа (ключа подписи), а если указана AT_KEYEXCHANGE, то ключ (ключ обмена) найдется.

Еще может быть имя контейнера указано неверно. Чтобы не путаться попробуйте открыть хранилище личные CertOpenSystemStore(0,'MY') перечислить сертификаты в хранилище CertEnumCertificatesInStore и получить ссылку на контейнер через CryptAcquireCertificatePrivateKey. Также функция возвратит нужную константу (AT_SIGNATURE или AT_KEYEXCHANGE) и из ссылки можно запросить имя контейнера CryptGetProvParam(hProv,PP_CONTAINER,...). А дальше по полученным точным данным можно обращаться к контейнеру и напрямую.

К слову, в контейнере еще возможны симметричный ключ и ключ УЭК, но они не совместимы с ключами обмена и подписи. Это к тому что строка "симметричный ключ отсутствует" также вполне нормальна.

Еще пара советов: отказаться от модулей идущих в составе дельфи (Crypt2, старые версии JwaWinCrypt) и скачать самый новый JwaWinCrypt. Функции уже изменились с тех пор и в старых версиях неверные объявления. Особенно это касается возможности обращаться из 64-разрядных программ: JwaWinCrypt в этом плане тоже не идеал, но заменить пару типов в начале проще чем рыскать по всему модулю. Если в новой версии какие-то функции испорчены (CryptStringToBinary), то они не работают, не нужно их пытаться исправить. Если захотите скопировать объявление в свой код - не забывайте stdcall.
Offline Андрей Т.  
#3 Оставлено : 12 февраля 2019 г. 2:51:24(UTC)
Андрей Т.

Статус: Новичок

Группы: Участники
Зарегистрирован: 11.02.2019(UTC)
Сообщений: 3
Российская Федерация
Откуда: Новосибирск

Благодарю Вас за столь развернутый ответ. Он сильно помог мне в понимании происходящего.
Ваши советы я обязательно учту. Использую JwaWinCrypt версии 1.13

Однако, при указании AT_KEYEXCHANGE результат не изменился.
Позволю себе привести код процедуры и результат выполнения.
Код:

procedure TForm1.GetProvData(ProvNum: Cardinal);
var
  ProvName, ContainerName: string;
  I, ProvType, NameLen, hProv, DataLen, impType, Flag, Error: Cardinal;
  Vers: array[0..3] of Byte;
  Key: HCRYPTKEY;
begin
  if not CryptEnumProviders(ProvNum, nil, 0, ProvType, nil, NameLen) then Exit;
  SetLength(ProvName, NameLen);
  if not CryptEnumProviders(ProvNum, nil, 0, ProvType, PChar(ProvName), NameLen) then Exit;

  Memo1.Lines.Append('  Len: '+IntToStr(NameLen)+' '+ProvName);
  Memo1.Lines.Append('  Provider type: '+ ProvTypeToStr(ProvType));

  if not CryptAcquireContext(hProv, nil, PChar(ProvName), ProvType, CRYPT_VERIFYCONTEXT {+ CRYPT_MACHINE_KEYSET}) then Exit;

  DataLen := 4;
  if not CryptGetProvParam(hProv, PP_VERSION, @Vers, DataLen, 0) then Exit;
  Memo1.Lines.Append('  Version: ' + IntToStr(Vers[1]) + '.' + IntToStr(Vers[0]));

  if not CryptGetProvParam(hProv, PP_IMPTYPE, @impType, DataLen, 0) then Exit;
  Memo1.Lines.Append('  Type: ' + ImpTypeToStr(impType));

  Flag := CRYPT_FIRST;
  CryptGetProvParam(hProv, PP_ENUMCONTAINERS, nil, DataLen, Flag);
  Memo1.Lines.Append('Контейнеры:  (Max size of container name: '+IntToStr(DataLen)+')');

  SetLength(ContainerName, DataLen+1);
  while True do
  begin
    if not CryptGetProvParam(hProv, PP_ENUMCONTAINERS, PByte(PChar(ContainerName)), DataLen, Flag) then
    begin
      Error := Cardinal(GetLastError);
      if Error <> ERROR_NO_MORE_ITEMS then
         Memo1.Lines.Append('       get container state: '+SysErrorMessage(Integer(Error)));
      break;
    end;

    Memo1.Lines.Add(#13+ContainerName);
    Flag := CRYPT_NEXT;

    if not CryptGetUserKey(hProv, {AT_SIGNATURE} AT_KEYEXCHANGE, Key) then
    begin
      Error := Cardinal(GetLastError);
      Memo1.Lines.Append('       get key handle: '+SysErrorMessage(Integer(Error)));
      Key := 0;
    end else
      Memo1.Lines.Append('       get key handle: success ('+IntToStr(Key)+')');

    if Key <> 0 then
      CryptDestroyKey(Key);
  end;

  CryptReleaseContext(hProv, 0);
end;



Код:

  Len: 60 Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider
  Provider type: Code: 75
  Version: 4.0
  Type: Code: 8
Контейнеры:  (Max size of container name: 513)

b6835a3e-4885-4831-b070-d98129d0665d
       get key handle: Ключ не существует

Offline two_oceans  
#4 Оставлено : 12 февраля 2019 г. 7:31:16(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 266
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 15 раз
Поблагодарили: 63 раз в 61 постах
К слову, у меня используется JwaWinCrypt 1.17

По коду ясно с чем затруднение - hProv это не просто какой-то экземпляр провайдера, а экземпляр провайдера, содержащий ссылку на контейнер. В случае использования флага CRYPT_VERIFYCONTEXT будет создан экземпляр провайдера, со ссылкой на "временный контейнер" без ключей - такой экземпляр, например, позволяет вычислять хэш, получать параметры провайдера, перечислять контейнеры, а также импортировать открытый ключ из сертификата для проверки подписи. Однако в данном экземпляре связки с существующим контейнером или закрытым ключом не было установлено. Поэтому на hProv созданном с CRYPT_VERIFYCONTEXT бесполезно использовать CryptGetUserKey пока туда что-то не импортировали.


Чтобы получить доступ к конкретному контейнеру, перед вызовом CryptGetUserKey нужно еще раз вызвать CryptAcquireContext с указанием имени контейнера и без флага CRYPT_VERIFYCONTEXT, допустим получили результат в hProv2. Тогда на hProv2 уже можно вызвать CryptGetUserKey и получить дескриптор ключевой пары (для проверки собственной подписи или использования в схеме обмена ключами при шифровании и расшифровке). Для создания подписи получать дескриптор ключа/пары не нужно: если для создания хэша CryptCreateHash использовать экземпляр провайдера, связанный с контейнером (как hProv2), то получим hHash, связанный с контейнером, далее считаем хэш CryptHashData и для подписания хэша остается вызвать CryptSignHash с указанием AT_SIGNATURE или AT_KEYEXCHANGE.

Отредактировано пользователем 12 февраля 2019 г. 7:39:11(UTC)  | Причина: Не указана

Offline Андрей Т.  
#5 Оставлено : 12 февраля 2019 г. 9:49:22(UTC)
Андрей Т.

Статус: Новичок

Группы: Участники
Зарегистрирован: 11.02.2019(UTC)
Сообщений: 3
Российская Федерация
Откуда: Новосибирск

Огромное спасибо. Очень доходчиво, точно. Все получилось.

Прочитал еще раз, с полученными знаниями, help по функции CryptAcquireContext и флагу CRYPT_VERIFYCONTEXT.
Пришел к выводу, что там написано именно то, что Вы объяснили. Но сделано там это так, что догадаться о правильном смысле нереально.
Ну или мой английский недостаточно хорош.

Еще раз, спасибо.
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.