Скриптдешифрованная криптопро и Рутокен ЭЦП. Техническая поддержка разработчиков Форума Рутокен

CryptDecryptMessage (crypt32)

Summary

TODO – a short description

Содержание
  1. C# Signature:
  2. VB Signature:
  3. User-Defined Types:
  4. Notes:
  5. Tips & Tricks:
  6. Sample Code:
  7. Alternative Managed API:
  8. Введение
  9. cryptoPro
  10. Зачем мне этот пакет?
  11. Установка
  12. API
  13. Методы объекта cryptoPro
  14. Методы объекта сертификата
  15. Поддерживаемые СКЗИ
  16. Примеры
  17. Тэг script (UMD)
  18. Angular (ES Modules + Typescript)
  19. React (ES Modules + JavaScript)
  20. Миграция с версии 1 на 2
  21. Тем, кто хочет помочь
  22. Запуск режима разработки
  23. Запуск тестов
  24. Проверка работы примеров с React и Angular
  25. Проверка пакета перед публикацией в NPM
  26. Полезная информация
  27. Установка КриптоПРО CSP в Linux / OSX
  28. Установка КриптоПРО ЭЦП browser plugin в Linux
  29. Настройка плагина для Firefox (до версии 52)
  30. Установка сертификатов в Linux
  31. Лицензия
  32. Please edit this page!
  33. Высокоуровневые функции обработки сообщений
  34. Введение
  35. Шифрование
  36. Расшифровывание
  37. Цифровая подпись
  38. Проверка цифровой подписи
  39. Совмещение цифровой подписи и шифрованных данных
  40. Расшифровывание и проверка совмещенной цифровой подписи и шифрованных данных
  41. Работа с форматом Base64 (PEM)
  42. CAPICOM
  43. Базовые функции
  44. Шифрование
  45. Экспорт сессионных ключей
  46. Импорт сессионных ключей
  47. Расшифровывание
  48. Хеширование
  49. Цифровая подпись
  50. Проверка цифровой подписи

C# Signature:

[DllImport(“crypt32.dll”, SetLastError=true)]

public static extern bool CryptDecryptMessage(

ref CRYPT_DECRYPT_MESSAGE_PARA pDecryptPara,

byte[] pbEncryptedBlob,

int cbEncryptedBlob,

[In, Out] byte[] pbDecrypted,

ref int pcbDecrypted,

ref IntPtr ppXchgCert);

VB Signature:

Declare Function CryptDecryptMessage Lib “crypt32.dll” (TODO) As TODO

User-Defined Types:

None.

Notes:

None.

Tips & Tricks:

Please add some!

Sample Code:

Please add some!

Alternative Managed API:

Do you know one? Please contribute it!

Введение

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

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

NPM version
NPM downloads
Build Status
Coverage Status
Examples
Donate
Скриптдешифрованная криптопро и Рутокен ЭЦП. Техническая поддержка разработчиков Форума Рутокен

cryptoPro

Единое, асинхронное API для взаимодействия с КриптоПРО ЭЦП Browser Plug-In

demo.gif
gosuslugi-demo-result.png
gosuslugi-signature-types.png

  • cryptoPro
  • Тем, кто хочет помочь
  • Полезная информация
  • Лицензия

Зачем мне этот пакет?

КриптоПРО ЭЦП Browser Plug-In доступен в разных браузерах в двух версиях.
Асинхронной (в современных браузерах) и синхронной (в браузерах постарше).
С помощью этого пакета можно не писать реализацию под каждую версию плагина дважды.
И вместо этого
и этого
написать это (UMD):

example-umd.gif

или это (ES Modules + Typescript):

example-esm.gif

Установка

Подключение пакета как UMD модуля через тэг script:

<div dir="auto" data-snippet-clipboard-copy-content="

window.cryptoPro.getUserCertificates()
.then(function (certificates) {
//…
})
.catch(function (error) {
//…
});
“>

 =""


    
    
  
    
    
  

Подключение пакета как ES модуля с Typescript или JavaScript:

{
let certificates: Certificate[];

try {
certificates = await getUserCertificates();
} catch(error) {
// …
}
})();”>

      

   
   : 

   
       
    
    
  

Список требуемых полифиллов (если необходимы, подключаются самостоятельно):

  • Promise
  • Array.prototype.find

API

Методы объекта cryptoPro

Методы объекта сертификата

Сертификат предоставляет следущее API:

Поддерживаемые СКЗИ

КриптоПРО CSP (v4.0+) рекомендуется использование только сертифицированных версий. Инструкция по установке:

  • Linux / OSX
  • (в Windows следуйте указаниям программы-установщика)

КриптоПРО ЭЦП browser plug-in (v2.0.12438+).

Инструкция по установке плагина в Linux. В Windows и OSX следуйте указаниям программы-установщика.

Инструкция по установке сертификатов в систему для Linux / OSX.

Примеры

Для их запуска необходим NodeJS LTS.

Тэг script (UMD)

 examples/script-tag
npm i
npm start

Angular (ES Modules + Typescript)

 examples/angular
npm i

Запуск в режиме разработки:

Запуск в продакшн режиме:

npm run build
npm run serve

React (ES Modules + JavaScript)

Запуск в режиме разработки:

Запуск в продакшн режиме:

npm run build
npm run serve

Миграция с версии 1 на 2

Внесены следующие изменения:

  • Пакет собран в форматах:
    • UMD, в папке dist/, для подключения через тэг script. Объект window.cryptoPro доступен глобально.
    • ES Modules, в папке lib/, для использования с разными системами сборки.
      Методы API импортируются напрямую из npm пакета.
  • В UMD версии переименован глобальный объект с window.CryptoPro на window.cryptoPro
  • Переименованы общие методы:
    • getCert -> getCertificate
    • isValidEDSSettings, isValidCSPVersion, isValidCadesVersion -> isValidSystemSetup
  • Убран метод signDataXML
  • Переименованы методы сертификата:
    • Результат методов сертификата getOwnerInfo и getIssuerInfo
      изменился с { descr, title, translated } на { description, title, isTranslated }
    • Принципиальная реализация методов, обращающихся к Крипто ПРО не изменилась.
      Получение сертификатов, создание подписи, проверка корректности настроек работают по-прежнему.
    • Убрана поддержка IE8 (Крипто ПРО его больше не поддерживает)
    • Убрана поддержка КриптоПРО CSP версий ниже 4.0
    • Убрана поддержка КриптоПРО ЭЦП browser plug-in версий ниже 2.0
    • Доработана обработка ошибок, выбрасываемых из Крипто ПРО
    • При написании кода будут работать автодополнения и подсказки
    • Исправлена проблема работы библиотеки с UglifyJs
    • Методы API доступны напрямую:

    В версии 1:

    В версии 2 (UMD):

    В версии 2 (ES Modules):

         
    
    

    Тем, кто хочет помочь

    Буду благодарен за расширение/улучшение/доработку API.
    Вам будут полезны примеры,
    предоставляемые Крипто ПРО.

    Запуск режима разработки

    Во время работы с кодом необходим запущенный сборщик:

    И пример, на котором можно тестировать изменения.
    Удобнее всего тестировать на примере с тэгом script, тк он отвязан от фреймворков
    и использует сборку в формате UMD из папки dist/, постоянно обновляемую пока работает
    сборщик. Запускаем его таким образом:

     examples/script-tag
    npm i
    npm link ../../
    npm start

    После выполнения npm link ../../ в директории examples/script-tag/node_modules папка crypto-pro станет ярлыком,
    указывающим на папку содержащую локально собранный пакет.

    Запуск тестов

    Тесты написаны с использованием Jest:

    Проверка работы примеров с React и Angular

    React и Angular используют версию сборки пакета в формате ES модулей из директории lib/.
    Для их запуска необходимо сначала собрать пакет выполнив:

    После этого из папки examples/angular или examples/react залинковать пакет:

     examples/angular
    npm i
    npm link ../../

    И запустить пример в одном из двух режимов. В режиме разработки:

    или в режиме продакшн:

    npm run build
    npm run serve

    Проверка пакета перед публикацией в NPM

    Необходимо протестировать работу в заявленных браузерах, сделав это на локально запакованной версии пакета.
    Для этого собираем пакет:

    npm run package
    mv package ..

    Важно переместить папку package куда-нибудь выше для избежания конфликтов при линковке с текущим package.json.

    Переходим в любую директорию с примером и создаем там ссылку на только что собранный пакет:

     examples/script-tag
    npm link ../../../package

    Проверяем работу примеров в режимах разработки и продакшн.

    После завершения экспериментов можно удалить глобальную ссылку из директории ../../../package таким образом:

     ../../../package
    npm unlink

    Полезная информация

    Установка КриптоПРО CSP в Linux / OSX

    Процесс установки в OSX незначительно отличается от Linux, поэтому описание приведено на примере дистрибутива семейства Debian (x64).

    Некоторые команды могут потребовать запуска с sudo.
    Названия файлов и директорий могут отличаться из-за различий в версиях.

    После загрузки КриптоПРО CSP для нужной платформы, распакуйте архив:

    tar -xzvf linux-amd64_deb.tgz
    chmod 777 -R linux-amd64_deb/

    Запустите скрипт установки:

    linux-amd64_deb/install.sh

    Проверьте отсутствие cprocsp-rdr-gui:

    dpkg -l  grep cprocsp-rdr

    Установите дополнительно cprocsp-rdr-gui-gtk:

    dpkg -i linux-amd64_deb/cprocsp-rdr-gui-gtk-64_4.0.0-4_amd64.deb

    Дополнительная информация по установке

    Установка КриптоПРО ЭЦП browser plugin в Linux

    Загрузите КриптоПРО ЭЦП browser plug-in и распакуйте его:

    mkdir cades_linux_amd64
    tar -xzvf cades_linux_amd64.tar.gz -C cades_linux_amd64

    Сконвертируйте rpm в deb пакеты при помощи утилиты alien:

    apt-get update  apt-get install alien
     cades_linux_amd64
    alien 

    dpkg -i cprocsp-pki-cades_2.0.0-2_amd64.deb
    dpkg -i cprocsp-pki-plugin_2.0.0-2_amd64.deb

    Проверьте наличие файлов плагина:

    libnpcades.so.2.0.0
    lrwxrwxrwx 1 root root 19 Окт 21 12:33 libnpcades.so.2 -> libnpcades.so.2.0.0
    -rwxr-xr-x 1 root root 2727236 Июн 8 14:33 libnpcades.so.2.0.0″>
    ls -la /opt/cprocsp/lib/amd64  grep libnpcades
    
        lrwxrwxrwx 1 root root      19 Окт 21 12:33 libnpcades.so - libnpcades.so.2.0.0
        lrwxrwxrwx 1 root root      19 Окт 21 12:33 libnpcades.so.2 - libnpcades.so.2.0.0
        -rwxr-xr-x 1 root root 2727236 Июн  8 14:33 libnpcades.so.2.0.0

    Настройка плагина для Firefox (до версии 52)

    После настройки плагина на страницах, запрашивающих работу с ЭП в панели навигации, рядом с url будет кнопка,
    позволяющая “разрешить и запомнить” использование установленного плагина.

     /usr/lib/mozilla/plugins
    
    cp /opt/cprocsp/lib/amd64/libnpcades.so.2.0.0 ./
    ldd libnpcades.so.2.0.0
    
    cp /opt/cprocsp/lib/amd64/libnpcades.so.2.0.0 ./libnpcades.so
    ldd libnpcades.so

    Перезапустите Firefox, и убедитесь в наличии CryptoPRO Cades plugin (см. Menu -&gt; Addons).

    Установка сертификатов в Linux

    В OSX процесс схож с Linux.

    Подключите USB носитель с ключевыми контейнерами и проверьте результат команды:

    /opt/cprocsp/bin/amd64/csptest -keyset -enum_cont -fqcn -verifyc
    
        CSP (Type:80) v4.0.9009 KC1 Release Ver:4.0.9797 OS:Linux CPU:AMD64 FastCode:READY:AVX.
            AcquireContext: OK. HCRYPTPROV: 16188003
            .LASHvanov
            .LASHetrov
            .LASHidorov
            .LASHasiliev
            .LASHmirnov
            OK.
            Total: SYS: 0,020 sec USR: 0,060 sec UTC: 0,180 sec

    Скопируйте ключевой контейнер \\.\FLASH\.\sidorov на жесткий диск:

    /opt/cprocsp/bin/amd64/csptest -keycopy -contsrc  -contdest 
    
        CSP (Type:80) v4.0.9009 KC1 Release Ver:4.0.9797 OS:Linux CPU:AMD64 FastCode:READY:AVX.
        CryptAcquireContext succeeded.HCRYPTPROV: 38556259
        CryptAcquireContext succeeded.HCRYPTPROV: 38770755
        Total: SYS: 0,000 sec USR: 0,100 sec UTC: 14,920 sec
        [ErrorCode: 0x00000000]

    Проверьте наличие нового контейнера \\.\HDIMAGE\sidor:

    /opt/cprocsp/bin/amd64/csptest -keyset -enum_cont -fqcn -verifyc
    
        CSP (Type:80) v4.0.9009 KC1 Release Ver:4.0.9797 OS:Linux CPU:AMD64 FastCode:READY:AVX.
        AcquireContext: OK. HCRYPTPROV: 34554467
        .LASHvanov
        .LASHetrov
        .LASHidorov
        .LASHasiliev
        .LASHmirnov
        .DIMAGEidor
        OK.
        Total: SYS: 0,010 sec USR: 0,050 sec UTC: 0,130 sec
        [ErrorCode: 0x00000000]

    Установите личный сертификат:

    /opt/cprocsp/bin/amd64/certmgr -inst -cont 

    Возможно в выводе вы ссылку на сертификат УЦ

    При необходимости загрузите сертификат удостоверяющего центра и установите его командой:

    <div dir="auto" data-snippet-clipboard-copy-content="/opt/cprocsp/bin/amd64/certmgr -inst -store uroot -file .crt”>

    /opt/cprocsp/bin/amd64/certmgr -inst -store uroot -file файл сертификата.crt

    После чего, при проверке установленного личного сертификата вы увидите PrivateKey Link: Yes:

    /opt/cprocsp/bin/amd64/certmgr -list -store uMy

    Лицензия

    Please edit this page!

    Do you have…

    • helpful tips or sample code to share for using this API in managed code?
    • corrections to the existing content?
    • variations of the signature you want to share?
    • additional languages you want to include?
    Читайте также:  Как установить КриптоПро?

    Select “Edit This Page” on the right hand toolbar and edit it! Or add new pages containing supporting types needed for this API (structures, delegates, and more).

    Скриптдешифрованная криптопро и Рутокен ЭЦП. Техническая поддержка разработчиков Форума Рутокен

    Высокоуровневые функции обработки сообщений

    Введение

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

    В основе работы данных функций лежит стандарт PKCS #7 (RFC 2315). Более подробно об этом стандарте будет рассказано несколько позднее, а здесь будет сказано лишь о его основных особенностях.

    Основу стандарта PKCS #7 составляют способы кодирования и описания различных данных, используемых в криптографических приложениях. Так, предоставляются стандарты по кодированию шифрованных и подписанных сообщений, стандарт кодирования сессионного ключа, стандарт кодирования информации о сертификате и многое другое. Работа со стандартом PKCS #7 реализована на многих платформах, отличных от MS Windows, что позволяет создавать кроссплатформенные приложения. Высокоуровневые функции скрывают многие не очень существенные моменты использования стандарта PKCS #7, благодаря чему упрощается работа с криптографическими данными.

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

    Шифрование

    Для шифрования используется функция CryptEncryptMessage. Данная функция имеет следующее описание:

    BOOL CryptEncryptMessage(PCRYPT_ENCRYPT_MESSAGE_PARA pEncryptPara, 
                             DWORD cRecipientCert, 
                             PCERT_CONTEXT rgpRecipientCert[],
                              BYTE* pbToBeEncrypted,
                             DWORD cbToBeEncrypted,
                             BYTE* pbEncryptedBlob,
                             DWORD* pcbEncyptedBlob);
    

    В качестве первого параметра данной функции передается указатель на структуру CRYPT_ENCRYPT_MESSAGE_PARA. В полях данной структуры передается информация об используемом для шифрования криптопровайдере, используемом алгоритме и вспомогательная информация, нужная для правильной работы выбранного алгоритма. Параметр cRecipientCert передает количество используемых сертификатов получателей. Сам массив используемых сертификатов получателей передается в параметре rgpRecipientCert.

    СОВЕТ

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

    Параметры pbToBeEncrypted и cbToBeEncrypted идентифицируют область обрабатываемых данных. Зашифрованный контент и его размер возвращается через параметры pbEncryptedBlob и pcbEncryptedBlob соответственно.

    Пример использования данной функции приведен ниже:

            
    HCERTSTORE  hSystemStore; 
    HCRYPTPROV hProv;
    
     (!(hSystemStore = CertOpenSystemStore(NULL, )))
    {
      Error();
      ;
    }
    
    std::cout << "System store is opened" << std::endl;
    
    PCCERT_CONTEXT pCert = NULL;
    
     (pCert = CertEnumCertificatesInStore(hSystemStore, pCert))
    {
      (!strcmp(pCert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, 
        ))
      {
        DWORD dwKeySpec;
        (CryptAcquireCertificatePrivateKey(pCert, 0, NULL, &hProv,
     &dwKeySpec, NULL))
          ;
      }
    }
    
    std::cout <<  << std::endl;
    
    
    CRYPT_ENCRYPT_MESSAGE_PARA EncryptPara;
    ZeroMemory(&EncryptPara, (EncryptPara));
    
    EncryptPara.cbSize = (EncryptPara);
    EncryptPara.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    EncryptPara.ContentEncryptionAlgorithm.pszObjId = 
      (LPSTR)CertAlgIdToOID(CALG_RC4);
    EncryptPara.hCryptProv = hProv;
    
     string[] = ; 
    
    (!CryptEncryptMessage(&EncryptPara, 1, &pCert, (BYTE*)string,
     strlen(string), NULL, count))
    {
      Error();
      ;
    }
    
    *encrypted = <BYTE*>(malloc(*count));
    
    (!CryptEncryptMessage(&EncryptPara, 1, &pCert, (BYTE*)string,
     strlen(string), *encrypted, count))
    {
      Error();
      ;
    }
    
    std::cout <<  << std::endl;
    
    CertCloseStore(hSystemStore, 0);
    CryptReleaseContext(hProv, 0);
    

    Расшифровывание

    Для расшифровывания зашифрованного контента применяется функция CryptDecryptMessage, имеющая следующее описание:

    BOOL CryptDecryptMessage(PCRYPT_DECRYPT_MESSAGE_PARA pDecryptPara,
                              BYTE* pbEncryptedBlob,
                             DWORD cbEncryptedBlob,
                             BYTE* pbDecrypted,
                             DWORD* pcbDecrypted,
                             PCCERT_CONTEXT* ppXchgCert);
    

    В качестве первого параметра данной функции передается указатель на структуру типа CRYPT_DECRYPT_MESSAGE_PARA. В полях данной структуры передается список хранилищ сертификатов, в которых будет производиться поиск сертификата для импорта сессионного ключа (сертификата обмена). В параметрах pbEncryptedBlob и cbEncryptedBlob передается информация о блоке входных данных, подлежащих расшифровке. В параметрах pbDecrypted и pcbDecrypted передаются принимающий буфер и его размер соответственно. В этот буфер будут помещены расшифровываемые данные. В параметре ppXchgCert возвращается ссылка на контекст сертификата, который был использован для обмена (если эта информация не нужна, то данный параметр должен быть установлен в NULL).

    ПРЕДУПРЕЖДЕНИЕ

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

    Пример использования этой функции приведен ниже:

    HCERTSTORE hSystemStore;
    
    (!(hSystemStore = CertOpenSystemStore(NULL, )))
    {
      Error();
      ;
    }
    
    std::cout << "System store is opened" << std::endl;
    
    
    CRYPT_DECRYPT_MESSAGE_PARA DecryptPara;
    ZeroMemory(&DecryptPara, (DecryptPara));
    
    DecryptPara.cbSize = (DecryptPara);
    DecryptPara.dwMsgAndCertEncodingType = 
      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    
    HCERTSTORE StoreArray[] = { hSystemStore };
    
    DecryptPara.cCertStore = 1;
    DecryptPara.rghCertStore = StoreArray;
    
    DWORD dwSize = 0;
    
    (!CryptDecryptMessage(&DecryptPara, encrypted, count, NULL, 
    &dwSize, NULL))
    {
      Error();
      ;
    }
    
    BYTE* decrypted = <BYTE*>(malloc(dwSize + 1));
    ZeroMemory(decrypted, dwSize + 1);
    
    (!CryptDecryptMessage(&DecryptPara, encrypted, count, decrypted,
     &dwSize, NULL))
    {
      Error();
      ;
    }
    
    std::cout <<  << std::endl;
    
    std::cout << "Result string: " << decrypted << std::endl;
    
    free(decrypted);
    free(encrypted);
    CertCloseStore(hSystemStore, 0);
    

    Цифровая подпись

    В отличие от простейшего случая формирования цифровой подписи базовыми функциями в стандарте PKCS предусмотрено существование двух видов цифровой подписи: подпись, совмещенная с подписываемыми данными (attached signature) и подпись, отдельная от данных (detached signature). Функция CryptSignMessage, формирующая оба эти вида подписей, имеет следующее описание:

    BOOL CryptSignMessage(PCRYPT_SIGN_MESSAGE_PARA pSignPara,
                          BOOL fDetachedSignature,
                          DWORD cToBeSigned,
                           BYTE* rgpbToBeSigned[],
                          DWORD rgcbToBeSigned[],
                          BYTE* pbSignedBlob,
                          DWORD* pcbSignedBlob);
    

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

    ПРЕДУПРЕЖДЕНИЕ

    Если флаг fDetachedSignature установлен в false, параметр cToBeSigned всегда должен быть равен 1. Это связано с особенностями реализации низкоуровневых функций работы с криптографическими сообщениями, особенности которых будут рассмотрены позднее.

    Параметр rgpbToBeSigned представляет собой массив данных, передаваемых для формирования цифровой подписи. Параметр rgcbToBeSigned представляет собой массив размеров элементов переданного массива данных. Выходное значение функции (цифровая подпись) формируется в параметрах pbSignedBlob и pcbSignedBlob.

    Пример использования данной функции приведен ниже:

            
    HCERTSTORE  hSystemStore; 
    
     (!(hSystemStore = CertOpenSystemStore(NULL, )))
    {
      Error();
      ;
    }
    
    std::cout << "System store is opened" << std::endl;
    
    PCCERT_CONTEXT pCert = NULL;
    
     (pCert = CertEnumCertificatesInStore(hSystemStore, pCert))
    {
      (!strcmp(pCert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, 
        ))
      {
        DWORD dwKeySpec;
        HCRYPTPROV hProv;
        (CryptAcquireCertificatePrivateKey(pCert, 0, NULL, &hProv,
     &dwKeySpec, NULL))
        {
          CryptReleaseContext(hProv, 0);
          ;
        }
      }
    }
    
    std::cout <<  << std::endl;
    
    
    CRYPT_SIGN_MESSAGE_PARA SignPara;
    ZeroMemory(&SignPara, (SignPara));
    
    SignPara.cbSize = (SignPara);
    SignPara.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    SignPara.HashAlgorithm.pszObjId = (LPSTR)CertAlgIdToOID(CALG_MD5);
    SignPara.pSigningCert = pCert;
    SignPara.cMsgCert = 1;
    SignPara.rgpMsgCert = &pCert;
    
     string[] = ;
    
     BYTE* DataArray[] = { (BYTE*)string };
    DWORD SizeArray[] = { strlen(string) };
    
    *count = 0;
    
    (!CryptSignMessage(&SignPara, , 1, DataArray, SizeArray, 
    NULL, count))
    {
      Error();
      ;
    }
    
    *signature = <BYTE*>(malloc(*count));
    
    (!CryptSignMessage(&SignPara, , 1, DataArray, SizeArray, 
    *signature, count))
    {
      Error();
      ;
    }
    
    std::cout << "Signature content is received" << std::endl;
    

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

    Для проверки цифровой подписи в состав высокоуровневых функций работы с сообщениями входят функции CryptVerifyMessageSignature и CrypVerifyDetachedMessageSignature. Первая функция предназначена для проверки цифровой подписи, совмещенной с данными. Результат проверки (правильна или неправильна цифровая подпись) можно узнать, проанализировав возвращаемое значение этой функции. Вторая функция предназначена для проверки цифровой подписи, не содержащей сами подписываемые данные. Первая из этих функций имеет следующее описание:

    BOOL CryptVerifyMessageSignature(PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara,
                                     DWORD dwSignerIndex,
                                      BYTE* pbSignedBlob,
                                     DWORD cbSignedBlob,
                                     BYTE* pbDecoded,
                                     DWORD* pcbDecoded,
                                     PCCERT_CONTEXT* ppSignerCert);
    

    В качестве первого параметра данной функции передается указатель на структуру типа CRYPT_VERIFY_MESSAGE_PARA. В полях данной структуры передается информация о контексте криптопровайдера, применяемом для проверки подписи, и ссылке на функцию, с помощью которой находится сертификат подписчика в локальных хранилищах данных. В случае, когда ссылка на данную функцию равна NULL, функция CryptVerifyMessageSignature ищет данный сертификат внутри самой цифровой подписи. Параметр dwSignerIndex задает номер подписчика, для которого необходимо проверить подпись (в одном файле подписи потенциально может быть несколько цифровых подписей от различных подписчиков). Для первого подписчика параметр dwSignerIndex должен быть равным 0. В параметрах pbSignedBlob и cbSignedBlob передается информация о входном блоке данных, подлежащем проверке. В параметрах pbDecoded и pcbDecoded может быть передана информация о блоке памяти, в который помещается раскодированное проверяемое сообщение (для которого, собственно, и была сформирована цифровая подпись). В случае, когда данная информация не нужна, параметры pbDecoded и pcbDecoded должны быть установлены в NULL. В параметре ppSignerCert возвращается двойной указатель на контекст сертификата подписчика.

    Пример использования этой функции примерно соответствует примеру для функции CryptVerifyDetachedMessageSignature, приведенному ниже.

    Функция, проверяющая отсоединенную от данных цифровую подпись, имеет следующее описание:

    BOOL CryptVerifyDetachedMessageSignature(PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara,
                                             DWORD dwSignerIndex,
                                              BYTE* pbDetachedSignedBlob,
                                             DWORD cbDetachedSignedBlob,
                                             DWORD cToBeSigned,
                                              BYTE* rgpbToBeSigned[],
                                             DWORD* rgcbToBeSigned[],
                                             PCCERT_CONTEXT* ppSignerCert);
    

    Параметры pVerifyPara и dwSignerIndex имеют точно такой же смысл, что и соответствующие параметры функции CryptVerifyMessageSignature. Параметры pbDetachedSignedBlob и cbDetachedSignedBlob описывают область памяти, которая хранит собственно проверяемые данные. Параметр cToBeSigned описывает количество элементов в массивах, передаваемых в параметрах rgpbToBeSigned и rgcbToBeSigned. Параметр rgpbToBeSigned представляет собой массив областей памяти, содержащий в себе данные, для которых проверяется цифровая подпись. Параметр rgcbToBeSigned содержит массив размеров блоков памяти, указанных в параметре rgpbToBeSigned. В параметре ppSignerCert возвращается двойной указатель на контекст сертификата подписчика. Результат проверки (правильна или неправильна цифровая подпись) возвращается как результат выполнения функции CryptVerifyDetachedMessageSignature.

    Читайте также:  1 июля уже близко – что дальше? ФНС отвечает на вопросы о развитии ЭДО и ЭП | Такском

    Пример использования данной функции приведен ниже:

    CRYPT_VERIFY_MESSAGE_PARA VerifyPara;
    ZeroMemory(&VerifyPara, (VerifyPara));
    
    VerifyPara.cbSize = (VerifyPara);
    VerifyPara.dwMsgAndCertEncodingType = X509_ASN_ENCODING | KCS_7_ASN_ENCODING;
    VerifyPara.hCryptProv = NULL;
    
     string[] = ;
    
     BYTE* DataArray[] = { (BYTE*)string };
    DWORD SizeArray[] = { strlen(string) };
    
    BOOL result = CryptVerifyDetachedMessageSignature(&VerifyPara, 0,
      signature, count, 1, DataArray, SizeArray, NULL);
    
    std::cout << "Result: signature "<< ((result) ? : ) << std::endl;
    

    Совмещение цифровой подписи и шифрованных данных

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

    Функция CryptSignAndEncryptMessage, осуществляющая подобную работу в Crypto API, имеет следующее описание:

    BOOL CryptSignAndEncryptMessage(PCRYPT_SIGN_MESSAGE_PARA pSignPara,
                                    PCRYPT_ENCRYPT_MESSAGE_PARA pEncryptPara,
                                    DWORD cRecipientCert,
                                    PCCERT_CONTEXT rgpRecipientCert[],
                                     BYTE* pbToBeSignedAndEncrypted,
                                    DWORD cbToBeSignedAndEncrypted,
                                    BYTE* pbSignedAndEncryptedBlob,
                                    DWORD* pcbSignedAndEncryptedBlob);
    

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

    Пример использования данной функции приведен ниже:

    HCRYPTPROV hProv;
    HCERTSTORE  hSystemStore; 
    
     (!(hSystemStore  =  CertOpenSystemStore(NULL, )))
    {
      Error();
      ;
    }
    
    std::cout << "System store is opened" << std::endl;
    
    PCCERT_CONTEXT pCert = NULL;
    
     (pCert = CertEnumCertificatesInStore(hSystemStore, pCert))
    {
      (!strcmp(pCert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, 
        ))
      {
        DWORD dwKeySpec;
         (CryptAcquireCertificatePrivateKey(pCert, 0, NULL, &hProv, 
           &dwKeySpec, NULL))
          ;
      }
    }
    
    std::cout <<  << std::endl;
    
    
    CRYPT_SIGN_MESSAGE_PARA SignPara;
    ZeroMemory(&SignPara, (SignPara));
    
    SignPara.cbSize = (SignPara);
    SignPara.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    SignPara.HashAlgorithm.pszObjId = (LPSTR)CertAlgIdToOID(CALG_MD5);
    SignPara.pSigningCert = pCert;
    SignPara.cMsgCert = 1;
    SignPara.rgpMsgCert = &pCert;
    
    
    CRYPT_ENCRYPT_MESSAGE_PARA EncryptPara;
    ZeroMemory(&EncryptPara, (EncryptPara));
    
    EncryptPara.cbSize = (EncryptPara);
    EncryptPara.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    EncryptPara.ContentEncryptionAlgorithm.pszObjId = 
      (LPSTR)CertAlgIdToOID(CALG_RC4);
    EncryptPara.hCryptProv = hProv;
    
     string[] = ;
    
     (!CryptSignAndEncryptMessage(&SignPara, &EncryptPara, 1, &pCert, 
        (BYTE*)string, strlen(string), NULL, count))
    {
      Error();
      ;
    }
    
    *encrypted = <BYTE*>(malloc(*count));
    
     (!CryptSignAndEncryptMessage(&SignPara, &EncryptPara, 1, &pCert, 
        (BYTE*)string, strlen(string), *encrypted, count))
    {
      Error();
      ;
    }
    
    std::cout << "Encrypted and signed content is received" << std::endl;
    
    CertFreeCertificateContext(pCert);
    CertCloseStore(hSystemStore, 0);
    CryptReleaseContext(hProv, 0);
    

    Расшифровывание и проверка совмещенной цифровой подписи и шифрованных данных

    Для расшифровывания и проверки совмещенных цифровой подписи и шифрованных данных применяют специальную функцию CryptDecryptAndVerifyMesageSignature, имеющую следующее описание:

    BOOL CryptDecryptAndVerifyMesageSignature(PCRYPT_DECRYPT_MESSAGE_PARA pDecryptPara,
                                              PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara,
                                              DWORD dwSignerIndex,
                                               BYTE* pbEncryptedBlob,
                                              DWORD cbEncryptedBlob,
                                              BYTE* pbDecrypted
                                              DWORD* pcbDecrypted,
                                              PCCERT_CONTEXT* ppXchgCert,
                                              PCCERT_CONTEXT* ppSignerCert);
    

    Значения параметров данной функции полностью аналогичны значениям параметров функций CryptDecryptMessage и CryptVerifyMessageSignature. Результат проверки (правильна или неправильна цифровая подпись) получается как результат функции CryptDecryptAndVerifyMessageSignature.

    Пример использования данной функции приведен ниже:

    HCERTSTORE hSystemStore;
    
    (!(hSystemStore  =  CertOpenSystemStore(NULL, )))
    {
      Error();
      ;
    }
    
    std::cout << "System store is opened" << std::endl;
    
    
    CRYPT_DECRYPT_MESSAGE_PARA DecryptPara;
    ZeroMemory(&DecryptPara, (DecryptPara));
    
    DecryptPara.cbSize = (DecryptPara);
    DecryptPara.dwMsgAndCertEncodingType = 
      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    
    HCERTSTORE StoreArray[] = {hSystemStore};
    
    DecryptPara.cCertStore = 1;
    DecryptPara.rghCertStore = StoreArray;
    
    
    CRYPT_VERIFY_MESSAGE_PARA VerifyPara;
    ZeroMemory(&VerifyPara, (VerifyPara));
    
    VerifyPara.cbSize = (VerifyPara);
    VerifyPara.dwMsgAndCertEncodingType = 
      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    VerifyPara.hCryptProv = NULL;
    
    BOOL result = CryptDecryptAndVerifyMessageSignature(&DecryptPara, 
      &VerifyPara, 0, encrypted, count, NULL, NULL, NULL, NULL);
    
    std::cout << "Decryption and verification completed" << std::endl;
    
    std::cout << "Result: string " << ((result)?:) 
      << std::endl;
    
    free(encrypted);
    CertCloseStore(hSystemStore, 0);
    

    Работа с форматом Base64 (PEM)

    Кроме стандарта PKCS #7 достаточно часто можно встретить употребление другого стандарта – PEM (Privacy-Exchanged Mail). Фактически же в данном стандарте применяются два последовательных кодирования: первичное в PKCS #7 и вторичное в Base64. То есть если мы после завершения шифрования высокоуровневой функцией CryptEncryptMessage закодируем полученный результат в кодировке Base64, то полученный результат можно принять за образец применения стандарта PEM.

    Работа с Base64 крайне проста и реализована во многих библиотеках. В частности, я бы хотел порекомендовать читателям средства, предоставляемые функциями Base64Encode и Base64Decode. Данные функции объявлены в заголовочном файле библиотеки ATL “atlenc.h”. Работа с данными функциями достаточно проста. Ниже приведен пример использования данных функций.

             data[] = ;
    
     len = 0;
    
    len = Base64EncodeGetRequiredLength((data),  ATL_BASE64_FLAG_NOCRLF); 
    LPSTR out = <LPSTR>(malloc(len + 1));
    ZeroMemory(out, len + 1);
    Base64Encode((BYTE*)data, (data), out, &len, ATL_BASE64_FLAG_NOCRLF);
    
    std::cout <<  << std::endl;
    
    
    std::cout << "Encoded string: " << out << std::endl;
    
    * decode = <*>(malloc(strlen(data) + 1));
    ZeroMemory(decode, strlen(data) + 1);
    
    Base64Decode(out, len, (BYTE*)decode, &len);
    
    std::cout <<  << std::endl;
    
    
    std::cout << "Decoded string: " << decode << std::endl;
    
    free(out);
    free(decode);
    

    CAPICOM

    Зачастую применение непосредственно функций Crypto API достаточно проблематично. Например, в Web-клиентах, где вызовы процедур напрямую невозможны. Для подобных целей, а также для упрощения работы с Crypto API был создан тип объектов CAPICOM (Crypto API COM-object). В своей реализации данный объект почти полностью охватывает все то, о чем говорилось выше в данной статье: от шифрования до работы с сертификатами. Благодаря использованию дуальных интерфейсов, доступ к данному объекту возможен как из клиентов с ранним связыванием (C++), так и с поздним (VBscript).

    В основе работы всех функций CAPICOM лежит использование высокоуровневых функций работы с криптографическими сообщениями, которые были рассмотрены ранее. Таким образом, базовым стандартом для выходных данных является стандарт PKCS #7, вторично кодированный Bas64. Так же, как и в случае высокоуровневых функций, работа ведется только с относительно небольшим участком данных, загруженных в память. Работа с данными большого объема или работа с поблочной загрузкой данных в объекте не предусмотрена.

    ПРЕДУПРЕЖДЕНИЕ

    Несмотря на то, что объект CAPICOM в качестве выходных данных использует общепринятый стандарт PKCS #7, используемый в функциях Crypto API, полной прямой совместимости между функциями Crypto API и CAPICOM нет. То есть выходная информация объекта CAPICOM не может быть использована в качестве входной для функций Crypto API. Дело в том, что в объекте CAPICOM происходит некоторое предварительное изменение входных данных перед выполнениями функций. То есть алгоритм выполнения, например, функции подписи, выглядит так:

    1. Получить входные данные.

    2. Изменить их (для внутреннего использования).

    3. Вызвать стандартные функции Crypto API.

    В качестве «изменения» входных данных используется простое дополнение к каждому символу исходной информации символа с кодом 0x00.

    Подробно работа с объектом типа CAPICOM в данной статье рассматриваться не будет, так как сам по себе объект не входит в состав Crypto API и рассказ о нем несколько выходит за рамки этой статьи.


    Эта статья опубликована в журнале
    RSDN Magazine

    #5-2004. Информацию о журнале можно найти здесь

    Базовые функции

    Шифрование

    Базовая функция шифрования данных имеет следующее объявление:

    BOOL CryptEncrypt(HCRYPTKEY hKey, 
                      HCRYPTHAS hHash, 
                      BOOL Final, 
                      DWORD dwFlags, 
                      BYTE* pbData, 
                      DWORD* pdwDataLen, 
                      DWORD dwBufLen);
    

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

    Эта функция может обрабатывать данные блоками. То есть нет необходимости сразу загружать в память целиком весь массив данных, а лишь потом передавать ссылку на него криптографической функции. Достаточно передавать массив данных поблочно, специальным образом отметив лишь последний блок данных (это обычно нужно, чтобы криптопровайдер провел некоторые действия после использования сессионного ключа). Для указания того, что это последний блок данных, в функции CryptEncrypt используется третий параметр Final. Четвертый параметр служит указателем на массив входных/выходных данных. Здесь нужно сразу отметить некоторую общую схему работы с данными в Crypto API. Если возвращаемые данные могут быть любого размера (а это возможно, ведь, скажем, в алгоритме может происходить простая замена, когда одна буква кодируется четырьмя цифрами), то работа с функцией состоит из двух этапов. На первом этапе в функцию передается общий размер входных данных и NULL в качестве ссылки на сам массив выходных данных. Функция возвращает длину выходного массива данных, пользователь инициализирует память необходимого размера и лишь затем заново передает функции ссылку на этот массив. Такая же схема используется и в работе с функцией CryptEncrypt. Параметр pdwDataLen служит для возврата размера данных, возвращаемых функцией. Параметр dwBufLen служит для указания длины входного буфера данных. Параметр dwFlags обычно не используется и устанавливается в 0.

    Пример использования функции CryptEncrypt приведен ниже:

    HCRYPTPROV hProv;
    HCRYPTKEY hSessionKey;
    
     (!CryptAcquireContext(&hProv, NULL, NULL, 
    PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    {
      Error();
      ;
    }
    
    std::cout << "Cryptographic provider initialized" << std::endl;
    
     (!CryptGenKey(hProv, CALG_RC4, 
    CRYPT_ENCRYPT | CRYPT_DECRYPT, &hSessionKey))
    {
      Error();
      ;
    }
    
    std::cout << "Session key generated" << std::endl;
    
     string[]=;
    DWORD count=strlen(string);
    
     (!CryptEncrypt(hSessionKey, 0, , 0, (BYTE*)string, 
    &count, strlen(string)))
    {
      Error();
      ;
    }
    
    std::cout <<  << std::endl;
    
    
    std::cout << "Encrypted string: " << string << std::endl;
    

    Экспорт сессионных ключей

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

    Базовая функция экспорта ключей имеет следующее описание:

    BOOL CryptExportKey(HCRYPTKEY hKey, 
                        HCRYPTKEY hExpKey, 
                        DWORD dwBlobType, 
                        DWORD dwFlags, 
                        BYTE* pdData, 
                        DWORD* pdwDataLen);
    

    Первым параметром данной функции передается хендл ключа, который будет экспортирован. Фактически, экспорт ключа можно представить как отдельную операцию шифрования ключа. Следовательно, для такой операции необходим еще один ключ шифрования. Обычно в Crypto API сессионный ключ шифруют с помощью асимметричного алгоритма. Параметр hExpKey в большинстве случаев инициализируют контекстом публичного ключа получателя. Параметр dwBlobType определяет формат получаемого блока экспорта. Возможно, скажем, указать, что экспорту будет подлежать только лишь публичный ключ. В этом случае параметр hExpKey должен быть равен 0 (шифрование публичного ключа не нужно) и на выходе функции получается простое значение публичного ключа. Для такого случая параметр dwBlobType должен быть равен PUBLICKEYBLOB. Обычно же, при экспорте сессионного ключа используется значение SIMPLEBLOB. Остальные значения данного параметра достаточно специфичны и применяются редко. Параметры pbData и pdwDataLen указывают на массив, выделенный для получения экспортируемого ключа, и на его размер.

    Читайте также:  Эцп как получить счет

    СОВЕТ

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

    Пример использования этой функции приведен ниже:

    HCRYPTPROV hProv;
    HCRYPTKEY hKey, hPublicKey, hNewKey;
    
    ключей (!CryptAcquireContext(&hProv, ,
     NULL, PROV_RSA_FULL, 0))
    ;
    
    std::cout << "Cryptographic provider initialized" << std::endl;
    
     (!CryptGenKey(hProv, CALG_RC4, 
    CRYPT_EXPORTABLE | CRYPT_ENCRYPT | CRYPT_DECRYPT, &hKey))
    ;
    
    std::cout << "Session key generated" << std::endl;
    
     (!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hPublicKey))
    ;
    
    std::cout << "Public key is received" << std::endl;
    
    count = 0;
    
     (!CryptExportKey(hKey, hPublicKey, SIMPLEBLOB, 0, NULL, &count)) 
    ;
    
    
    BYTE* data = <BYTE*>(malloc(count)); 
    ZeroMemory(data, count);
    
     (!CryptExportKey(hKey, hPublicKey, SIMPLEBLOB, 0, data, &count)) 
    ;
    
    std::cout << "Key's export completed" << std::endl;
    

    Импорт сессионных ключей

    Базовая функция импорта ключей имеет следующее описание:

    BOOL CryptImportKey(HCRYPTPROV hProv, 
                        BYTE* pbData, 
                        DWORD dwDataLen, 
                        HCRYPTKEY hPubKey, 
                        DWORD dwFlags, 
                        HCRYPTKEY* phKey);
    

    В качестве первого параметра в данную функцию передается инициализированный контекст криптопровайдера. Должен отметить, что для успешного завершения работы функции CryptImportKey необходимо, чтобы при инициализации криптопровайдера был указан контейнер ключей. В частности, это необходимо для успешного импорта секретных ключей в контейнер ключей. Параметр pbData представляет собой ссылку на импортируемые данные, параметр dwDataLen – длину этих данных. В параметре hPubKey указывают хендл ключа, применяемого при импорте (для расшифровывания сессионного ключа). Параметр dwFlags обычно не применяется и может быть установлен в 0. В параметре phKey возвращается импортированный ключ.

    Пример использования данной функции приведен ниже:

            
            (!CryptImportKey(hProv, data, count, hPublicKey, 0, &hNewKey))
      ;
    
    std::cout << "Key's import completed"<< std::endl;
    

    Расшифровывание

    Базовая функция расшифровывания имеет следующее описание:

    BOOL CryptDecrypt(HCRYPTKEY hKey, 
                      HCRYPTHASH hHash, 
                      BOOL Final, 
                      DWORD dwFlags, 
                      BYTE* pbData, 
                      DWORD* pdwDataLen);
    

    Первым параметром данной функции передается инициализированный контекст сессионного ключа, применяемого для расшифровывания данных. Второй параметр, как и в предыдущем примере, связан, по большей части, с функцией получения и проверки цифровой подписи. Обычно он не используется и устанавливается в 0. Параметр dwFlags чаще всего не используется и также устанавливается в 0. Параметры pbData и pdwDataLen используются точно так же, как и у CryptEncryptи представляют собой ссылку на входной/выходной массив данных и длину этого массива данных.

    Пример использования функции CryptDecrypt приведен ниже:

            
            (!CryptDecrypt(hSessionKey, 0, , 0, (BYTE*)string, &count))
    {
      Error();
      ;
    }
    
    std::cout << << std::endl;
    
     
    std::cout << "Decrypted string: "<< string << std::endl;
    
     
    CryptDestroyKey(hSessionKey); 
    CryptReleaseContext(hProv, 0);
    

    Хеширование

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

    В Crypto API для манипуляции с хэшем используется специальный хэш-объект. Взаимодействие с этим объектом осуществляется с помощью следующих трех функций:

    • CryptCreateHash;
    • CryptHashData;
    • CryptGetHashParam.

    Для первичной инициализации хэш-объекта применяют функцию CryptCreateHash. Данная функция имеет следующее описание:

    BOOL CryptCreateHash(HCRYPTPROV hProv, 
                         ALG_ID AlgId, 
                         HCRYPTKEY hKey, 
                         DWORD dwFlags, 
                         HCRYPTHASH* phHash);
    

    В качестве первого параметра данной функции передается инициализированный контекст криптопровайдера. Вторым параметром указывается алгоритм получения значения хеша. Параметр hKey необходим лишь в случае применения специализированных алгоритмов типа MAC и HMAC.

    СОВЕТ

    MAC (Message Authentication Code, русский термин – «имитовставка»), переводится как «код проверки подлинности сообщения». Фактически, MAC предназначен для проверки значения хеш-значения только людьми, имеющими необходимый ключ. Можно воспринимать это как дополнительное шифрование значения, полученного с помощью хеш-функции (хотя это и не совсем так). Также MAC можно использовать в качестве простейшей цифровой подписи. HMAC (Hash-based Message Authentication Code) является разновидностью MAC.

    Параметр dwFlags зарезервирован под возможное будущее использование и должен быть всегда равен 0. Через параметр phHash функция возвращает хендл созданного ей хеш-объекта. После того, как хеш-объект станет ненужным, нужно освободить хеш-объект с помощью вызова функции CryptDestroyHash.

    После инициализации хеш-объекта можно начать передачу данных хеш-функции с помощью вызова CryptHashData. Данная функция имеет следующее описание:

    BOOL CryptHashData(HCRYPTHASH hHash, 
                       BYTE* pbData, 
                       DWORD dwDataLen, 
                       DWORD dwFlags);
    

    В качестве первого параметра данной функции передается ранее инициализированный хендл хеш-объекта. Вторым параметром передается порция данных для хеш-функции. Параметр dwDataLen представляет собой длину передаваемых данных. Параметр dwFlags обычно равен нулю.

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

    BOOL CryptGetHashParam(HCRYPTHASH hHash, 
                           DWORD dwParam, 
                           BYTE* pbData, 
                           DWORD* pdwDataLen, 
                           DWORD dwFlags);
    

    В качестве первого параметра данной функции передается ранее инициализированный хендл хеш-объекта. Второй параметр, dwParam, функции определяет тип запрашиваемого значения. Для получения хеш-значения необходимо передать вторым аргументом значение HP_HASHVAL. Параметры pdData и pdwDataLen отвечают за блок памяти, используемый под возвращаемое значение. Параметр dwFlags зарезервирован для будущего использования и должен быть равен нулю.

    Для проверки правильности хеш-значения нужно получить хэш-значение данных и сверить его с проверяемым хэш-значением.

    Пример получения хеш-значения приведен ниже:

    HCRYPTPROV hProv;
    HCRYPTHASH hHash;
    
    (!CryptAcquireContext(&hProv, 
    , NULL, PROV_RSA_FULL, 0))
    {
      Error();
      ;
    }
    
    std::cout << "Cryptographic provider initialized" << std::endl;
    
    (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
    {
      Error();
      ;
    }
    
    std::cout <<  << std::endl;
    
     string[] = ;
    DWORD count = strlen(string);
    
    (!CryptHashData(hHash, (BYTE*)string, count, 0))
    {
      Error();
      ;
    }
    
    std::cout << "Hash data loaded" << std::endl;
    
    
    count = 0;
    
    (!CryptGetHashParam(hHash, HP_HASHVAL, NULL, &count, 0))
    {
      Error();
      ;
    }
    
    * hash_value = <*>(malloc(count + 1));
    ZeroMemory(hash_value, count + 1);
    
    (!CryptGetHashParam(hHash, HP_HASHVAL, (BYTE*)hash_value, &count, 0))
    {
      Error();
      ;
    }
    
    std::cout << "Hash value is received" << std::endl;
    
    
    std::cout << "Hash value: " << hash_value << std::endl;
    

    Цифровая подпись

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

    Именно такой подход к формированию цифровой подписи используется в базовых функциях Crypto API для работы с подписью. Базовая функция получения подписи хеша данных имеет следующее описание:

    BOOL CryptSignHash(HCRYPTHASH hHash, 
                       DWORD dwKeySpec, 
                       LPCTSTR sDescription, 
                       DWORD dwFlags, 
                       BYTE* pbSignature, 
                       DWORD* pdwSigLen);
    

    В качестве первого параметра используется значение хендла хеш-объекта, уже инициализированного данными (с помощью функции CryptHashData). Параметр dwKeySpec определяет, какая именно пара ключей будет использована для формирования подписи (AT_KEYEXCHANGE (пара для обмена ключами) или AT_SIGNATURE (пара для формирования цифровой подписи)). Еще раз хочется обратить внимание читателя, что во многих криптопровайдерах пара ключей, предназначенная для обмена ключами, может также использоваться и для формирования цифровой подписи (но не во всех криптопровайдерах). Параметр sDescription более не используется в данной функции и его значение должно всегда быть установлено в NULL. Параметр dwFlags обычно устанавливают также в 0. Параметры pbSignature и pdwSigLen используют для корректного указания ссылки на массив выходных данных и его размера.

    Пример использования данной функции приведен ниже:

            
    count = 0;
    
    (!CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, NULL, &count))
    {
      Error();
      ;
    }
    
    * sign_hash = <*>(malloc(count + 1));
    ZeroMemory(sign_hash, count + 1);
    
    (!CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, (BYTE*)sign_hash, &count))
    {
      Error();
      ;
    }
    
    std::cout <<  << std::endl;
    
    
    std::cout << "Signature value: " << sign_hash << std::endl;
    

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

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

    BOOL CryptVerifySignature(HCRYPTHASH hHash, 
                              BYTE* pbSignature, 
                              DWORD dwSigLen, 
                              HCRYPTKEY hPubKey, 
                              LPCTSTR sDescription, 
                              DWORD dwFlags);
    

    В качестве первого параметра в функцию передается хендл хеш-объекта, предварительно инициализированный данными посредством функции CryptHashData. Второй и третий параметры отвечают за передачу значения проверяемой подписи. Параметр hPubKey используется для указания хендла публичного ключа отправителя подписи (того, кто собственно сформировал цифровую подпись). Параметр sDescription в настоящее время более не используется и его значение должно быть установлено в NULL. Параметр dwFlags также обычно не несет полезной нагрузки и устанавливается в 0.

    Пример использования данной функции приведен ниже:

            
    HCRYPTKEY hPublicKey;
    
    (!CryptGetUserKey(hProv, AT_SIGNATURE, &hPublicKey))
    {
      Error();
      ;
    }
    
    std::cout << "Public key is received" << std::endl;
    
    
    BOOL result = CryptVerifySignature(hHash, (BYTE*)sign_hash, count,
      hPublicKey, NULL, 0);
    
    std::cout << "Check is completed" << std::endl;
    
    
    std::cout << "Check result: " 
      << ((result)?  : ) << std::endl;
    

    Оцените статью
    ЭЦП Эксперт
    Добавить комментарий