Сообщений 6    Оценка 206        Оценить  
Система Orphus

Создание и верификация цифровой подписи в CryptoAPI через сертификат открытого ключа

Автор: Евгений Грищенко
Источник: RSDN Magazine #1
Опубликовано: 14.10.2002
Исправлено: 13.03.2005
Версия текста: 1.0


Верификация цифровой подписи в формате PKCS #7
Верификация цифровой подписи без дополнительной информации
Cоздание цифровой подписи в формате PKSC#7
Cоздание обычной цифровой подписи

Демонстрационные проекты (VC7)

В рамках статьи рассматривается создание и проверка цифровой подписи с использованием алгоритма шифрования RSA (названного по именам создателей – Rivest, Shamir и Adleman), самого популярного алгоритма для работы с открытым ключом и хеширования по методу MD5 (Message Digest 5). Идея цифровой подписи заключается в следующем. На основе сообщения, которое требуется подписать, создается хеш – уникальная последовательность байт определенной длины – 128 бит. Хеш шифруется через алгоритм шифрования RSA с применением закрытого ключа. Полученная последовательность байт называется цифровой подписью данного сообщения.

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

Сегодня все технологии передачи защищенных данных в Internet – обмен ключами, работа с сертификатами, алгоритмы шифрования - должны соответствовать стандарту PEM (Privacy-Enhanced Mail). Протокол X.509, который описывает структуру сертификата, является составной частью PEM.

Version
Serial Number
Algorithm Identifier:Algorithm Parameters
Issuer
Period of Validity:Not Before Date Not After Date
Subject
Subject’s Public KeyAlgorithmParametersPublic Key
Signature
Структура X.509-сертификата

Верификация цифровой подписи в формате PKCS #7

Функции CryptoAPI прекрасно работают с сертификатами, но подпись этими функциями создается в формате Public-Key Cryptography Standards #7 (PKCS #7). Формат PKCS #7 предполагает, что к подписи присоединяется заголовок с информацией, взятой из сертификата. В результате можно проверять и создавать только подпись в формате PKCS #7. Задача состоит в том, чтобы научиться создавать и верифицировать подпись без этой добавочной информации, и при этом работать с ней через сертификат. Для этого сначала рассмотрим стандартный способ верификации цифровой подписи в формате PKCS #7. Ниже приведен сертификат открытого ключа X.509 (файл evgeny.cer).

-----BEGIN CERTIFICATE-----
MIIEYzCCA8ygAwIBAgIKYV7rWgAAAAAACjANBgkqhkiG9w0BAQQFADB/MR8wHQYJ
KoZIhvcNAQkBFhBldmdAaW50b3VyaXN0LnJ1MQswCQYDVQQGEwJSVTEMMAoGA1UE
CBMDTU9TMQ8wDQYDVQQHEwZNb3Njb3cxEjAQBgNVBAoTCUludG91cmlzdDEMMAoG
A1UECxMDSXZkMQ4wDAYDVQQDEwVDQUV2ZzAeFw0wMjAzMTMwNzUxMzNaFw0wMzAz
MTMwODAxMzNaMH0xHzAdBgkqhkiG9w0BCQEWEGV2Z0BpbnRvdXJpc3QucnUxCzAJ
BgNVBAYTAlJVMQwwCgYDVQQIEwNNT1MxDzANBgNVBAcTBk1vc2NvdzEPMA0GA1UE
ChMGRXZnZW55MQwwCgYDVQQLEwNJdmQxDzANBgNVBAMTBkV2Z2VueTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEApfbC9o+VdcTInJVQvVerXKwEfdvUx/2JKyhO
dpDDkiURkSKF8SUlpFR0HnZPx3otSMTAZxM3lXW4gZfPvGQXDyIiYoCz+u9jVv95
KPI09wH5CbcmnH6cCXhxOwPEkac7ZLRDfgdWacr2WILafmIQY6etLXpvYOXgVzgc
tLugfpECAwEAAaOCAeYwggHiMA4GA1UdDwEB/wQEAwIE8DATBgNVHSUEDDAKBggr
BgEFBQcDAjAdBgNVHQ4EFgQUDgCDFfHQI/BQ7FpniwzlqkAY4UEwgboGA1UdIwSB
sjCBr4AUcCNURZ+qsHWzJIDCtT9fG2rOW1mhgYSkgYEwfzEfMB0GCSqGSIb3DQEJ
ARYQZXZnQGludG91cmlzdC5ydTELMAkGA1UEBhMCUlUxDDAKBgNVBAgTA01PUzEP
MA0GA1UEBxMGTW9zY293MRIwEAYDVQQKEwlJbnRvdXJpc3QxDDAKBgNVBAsTA0l2
ZDEOMAwGA1UEAxMFQ0FFdmeCEB5RCIYxLAirQDpDM590GewwXwYDVR0fBFgwVjAo
oCagJIYiaHR0cDovL2V2Zy1paS9DZXJ0RW5yb2xsL0NBRXZnLmNybDAqoCigJoYk
ZmlsZTovL1xcZXZnLWlpXENlcnRFbnJvbGxcQ0FFdmcuY3JsMH4GCCsGAQUFBwEB
BHIwcDA1BggrBgEFBQcwAoYpaHR0cDovL2V2Zy1paS9DZXJ0RW5yb2xsL2V2Zy1p
aV9DQUV2Zy5jcnQwNwYIKwYBBQUHMAKGK2ZpbGU6Ly9cXGV2Zy1paVxDZXJ0RW5y
b2xsXGV2Zy1paV9DQUV2Zy5jcnQwDQYJKoZIhvcNAQEEBQADgYEALtgCrgHa0g/p
Ej5+aNOP/Nx6nyQRGzKHvZQ25mea35WbNCMKlT8XrPRM+GFlIk6Y5gf6ms8KddNK
7tJ0x1hD3fcPMGbRfewu5YoLrykRSLepxbW8OIJCkM+CxBNtxdeMD/h2OJ9qAZfY
aPyo4NYsIoO4kIv5R9QjXgLZt3B1R3U=
-----END CERTIFICATE-----

При помощи этого сертификата проверим PKCS #7 подпись.

Установим этот сертификат. Двойным щелчком по файлу evgeny.cer запускаем certificate import wizard. Выбираем для установки персональное хранилище (personal) и заканчиваем установку, нажав кнопку Finish.

Возьмем сообщение, которое мы предположительно получили, «ВАО Интурист - Туроператор N1», заверенное цифровой подписью в формате PKCS #7, приведенной ниже.

3082016e06092a864886f70d010702a082015f3082015b020101310e300c0608
2a864886f70d02050500300b06092a864886f70d010701318201373082013302
010130818d307f311f301d06092a864886f70d010901161065766740696e746f
75726973742e7275310b3009060355040613025255310c300a06035504081303
4d4f53310f300d060355040713064d6f73636f7731123010060355040a130949
6e746f7572697374310c300a060355040b1303497664310e300c060355040313
054341457667020a615eeb5a00000000000a300c06082a864886f70d02050500
300d06092a864886f70d010101050004818071df37e9a80308355029533ddb70
5c9e5e8bd0b9c5e582d9623f134797a43c49bab00f25368d127f3870366b42dd
af71a0d5d475bfb44f9482ec918baf835d9a059e238b12c1945811b78423d8f2
11468c6c262099d604e527cefb22bec9a32ccd51624be3bd7684e502d212a6fc
1c2e6f132582df7833bcca9a44c8ad877366

Данная цифровая подпись преобразована в hex-вид, поэтому при верификации ее нужно преобразовать обратно в текстовый формат.

Алгоритм верификации состоит из вызова следующих функций:

// Верификация цифровой подписи, представленной в формате PKSC#7
// Необходимо подключить библиотеку crypt32.lib
// Компиляция с использованием Visual Studio 7.0

#include <stdio.h>
#include <stdlib.h> //atoi()

#include <wtypes.h>
#include <wincrypt.h>

#define MY_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

// Наименование персонального хранилища
#define CERT_STORE_NAME  L"MY"

// Наименование сертификата, установленного в это хранилище
#define SIGNER_NAME  L"EVGENY"

// функция обратного вызова для структуры 
// CRYPT_VERIFY_MESSAGE_PARA VerifyParams;
PCCERT_CONTEXT WINAPI MyGetSignerCertificateCallback(
  void *pvGetArg,                  // in
  DWORD dwCertEncodingType,        // in
  PCERT_INFO pSignerId,            // in
  HCERTSTORE hMsgCertStore         // in
)
{
  return PCCERT_CONTEXT(pvGetArg);
};

// перевод hex-подписи в бинарное представление
int hex2bin(const char* hexsign, BYTE* pbarray);


void HandleError(char *s);

int main(int argc, char* argv[])
{
//--------------------------------------------------------------------
// Сообщение, которое мы получили

  BYTE* pbMessage = (BYTE*)"ВАО Интурист - Туроператор N1";
  DWORD cbMessage = (DWORD)strlen((char*) pbMessage)+1;    

// Цифровая подпись к сообщению в формате PKSC#7
  const char* hexsign = 
"3082016e06092a864886f70d010702a082015f3082015b020101310e300c0608"
"2a864886f70d02050500300b06092a864886f70d010701318201373082013302"
"010130818d307f311f301d06092a864886f70d010901161065766740696e746f"
"75726973742e7275310b3009060355040613025255310c300a06035504081303"
"4d4f53310f300d060355040713064d6f73636f7731123010060355040a130949"
"6e746f7572697374310c300a060355040b1303497664310e300c060355040313"
"054341457667020a615eeb5a00000000000a300c06082a864886f70d02050500"
"300d06092a864886f70d010101050004818071df37e9a80308355029533ddb70"
"5c9e5e8bd0b9c5e582d9623f134797a43c49bab00f25368d127f3870366b42dd"
"af71a0d5d475bfb44f9482ec918baf835d9a059e238b12c1945811b78423d8f2"
"11468c6c262099d604e527cefb22bec9a32ccd51624be3bd7684e502d212a6fc"
"1c2e6f132582df7833bcca9a44c8ad877366";

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

  DWORD cbArray = (DWORD)strlen(hexsign)/2 ;
  BYTE* pbArray = new BYTE[cbArray];

// Перевод цифровой подписи в бинарное представление
  if (hex2bin(hexsign, pbArray))
  {
    printf("Ошибка hex2bin.\n");
    exit(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( "Сертификат не найден.");
  }

  CRYPT_VERIFY_MESSAGE_PARA VerifyParams;

// Заполнение структуры для верификации

  VerifyParams.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
  VerifyParams.dwMsgAndCertEncodingType = MY_TYPE;
  VerifyParams.hCryptProv = 0;
  VerifyParams.pfnGetSignerCertificate = MyGetSignerCertificateCallback;
  VerifyParams.pvGetArg = (void*)pSignerCert;

  const BYTE* MessageArray[] = {pbMessage};
  DWORD MessageSizeArray[1];
  MessageSizeArray[0] = cbMessage;

// верификация подписи
  if(CryptVerifyDetachedMessageSignature(
    &VerifyParams,          // указатель на структуру VerifyParams
    0,                      // 
    pbArray,    // указатель на подпись
    cbArray,    // длина подписи
    1,                      // число сообщений
    MessageArray,           // сообщение
    MessageSizeArray,       // длина сообщения
    &pSignerCert))      // указатель на сертификат
  {
    printf("Верификация прошла успешно!.\n");
  }
  else
  {
    HandleError("Верификация не прошла.");
  }
  
// Освобождаем память

  if(pbArray)
    delete pbArray;

  if(pSignerCert)
    CertFreeCertificateContext(pSignerCert);

  if(CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))
  {
    printf("Хранилище закрыто. \n");
  }
  else
  {
    printf("Ошибка!");
  }

  return 0;
}

void HandleError(char *s)
{
    printf("Ошибка. \n");
    printf("%s\n",s);
    printf("Ошибка N %x.\n", GetLastError());
    printf("Программа завершена с ошибкой. \n");
    exit(1);
} 

int hex2int(char ch) throw(...)
{
  if (ch >= '0' && ch <= '9') return ch - '0';
  if (ch >= 'a' && ch <= 'f') return ch - 'a' + 0xA;
  if (ch >= 'A' && ch <= 'F') return ch - 'A' + 0xA;
  throw 1;    
}

int hex2bin(const char* hexsign, BYTE* pbarray)
{
  try{
    while(*hexsign)
    {
      int aa = hex2int(*hexsign++); 
      int bb = hex2int(*hexsign++);
      *pbarray++ = (BYTE)(aa << 4 | bb); 
    }
  }
  catch(...) {
    return -1;
  }
  return 0;
}

Результат должен быть следующим:

Сертификат найден.
Верификация прошла успешно!.
Хранилище закрыто.

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

А теперь, предположим, вы получили цифровую подпись без дополнительной информации в hex представлении. Ее длина 256 байт или 128 байт в текстовом виде.

"667387adc8449acabc3378df8225136f2e1cfca612d202e58476bde34b6251cd"
"2ca3c9be22fbce27e504d69920266c8c4611f2d82384b7115894c1128b239e05"
"9a5d83af8b91ec82944fb4bf75d4d5a071afdd426b3670387f128d36250fb0ba"
"493ca49747133f62d982e5c5b9d08b5e9e5c70db3d532950350803a8e937df71";

Вы также получили и сертификат с открытым ключом. Используя только функции для работы с сертификатами, вы не сможете верифицировать такую подпись. Для верификации этой подписи нужно перейти к функциям CryptoAPI нижнего уровня, которые работают с CSP (cryptographic service provider). Все криптографические операции в операционной системе выполняются независимыми друг от друга модулями, известными как криптографические сервис провайдеры (CSP). В поставку Windows 2000 входят несколько CSP:

Каждый CSP имеет свою базу ключей (key database), в которой находится один или несколько контейнеров (key containers), где хранятся пары закрытых/открытых ключей. При установке сертификата в операционную систему, в базе ключей соответствующего CSP (в нашем случае - Microsoft Base Cryptographic Provider v1.0) создается контейнер. Для проверки этой подписи, необходимо перейти к работе с CSP и контейнеру нашего сертификата через функцию CryptAcquireCertificatePrivateKey. В ходе верификации получим для справки имя CSP, уникальное имя контейнера ключей сертификата, а также значение MD5-хеша сообщения.

Процесс верификации заключается в вызове следующих функций:

// Верификация цифровой подписи без дополнительной информации
// Необходимо подключить библиотеку crypt32.lib
// Компиляция с использованием Visual Studio 7.0
#include <stdio.h>
#include <stdlib.h> // exit(1);
#include <wtypes.h>
#include <wincrypt.h>

#define MY_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

// Наименование персонального хранилища
#define CERT_STORE_NAME  L"MY"

// Наименование сертификата, установленного в это хранилище
#define SIGNER_NAME  L"EVGENY"

// перевод hex-подписи в бинарное представление
int hex2bin(const char* hexsign, BYTE* pbarray);

void HandleError(char *s);

int main(int argc, char* argv[])
{
//--------------------------------------------------------------------
// Сообщение, которое мы получили
  BYTE* pbMessage = (BYTE*)"ВАО Интурист - Туроператор N1";
  DWORD cbMessage = (DWORD)strlen((char*) pbMessage)+1;    

// Цифровая подпись к сообщению в hex-представлении
  const char* hexsign = 
    "667387adc8449acabc3378df8225136f2e1cfca612d202e58476bde34b6251cd"
    "2ca3c9be22fbce27e504d69920266c8c4611f2d82384b7115894c1128b239e05"
    "9a5d83af8b91ec82944fb4bf75d4d5a071afdd426b3670387f128d36250fb0ba"
    "493ca49747133f62d982e5c5b9d08b5e9e5c70db3d532950350803a8e937df71";

// Переменные для указателя и длины подписи в текстовом виде
  DWORD cbArray = (DWORD)strlen(hexsign)/2 ;
  BYTE* pbArray = new BYTE[cbArray];

// Перевод цифровой подписи в бинарное представление
  if (hex2bin(hexsign, pbArray))
  {
    printf("Ошибка hex2bin.\n");
    exit(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
// одна из самых интересных и полезных функций в CryptoAPI
  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");
  }

  DWORD dwUserNameLen = 100;    
  CHAR szUserName[100];
  
  if(CryptGetProvParam(
    hProv,              // Дескриптор на CSP
    PP_NAME,            // параметр для получения имени CSP
    (BYTE *)szUserName, // Указатель на буфер, содержащий имя CSP
    &dwUserNameLen,     // длина буфера 
    0)) 
  {
    printf("Имя CSP: %s\n",szUserName);
  }
  else
  {
    HandleError("Ошибка CryptGetProvParam.\n");
  }

  if(CryptGetProvParam(
    hProv,              // Дескриптор на CSP
    PP_CONTAINER,       // параметр для получения имени key container  
    (BYTE *)szUserName, // Указатель на буфер, содержащий имя key container
    &dwUserNameLen,     // длина буфера
    0)) 
  {
    printf("Имя key container: %s\n",szUserName);
  }
  else
  {
    HandleError("Ошибка CryptGetProvParam.\n");
  }

// Импортируем public key для последующей верификации
  HCRYPTKEY hPubKey;

  if(CryptImportPublicKeyInfo(
    hProv, 
    MY_TYPE, 
    &(pSignerCert->pCertInfo->SubjectPublicKeyInfo),
    &hPubKey
  ))
  {
    printf("Импортирован public key.\n");
  }
  else
  {
    HandleError("Ошибка CryptAcquireContext.");
  }

// Создаем пустой 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 объект вычислен: ");
  }
  else
  {
    HandleError("Ошибка  CryptHashData.");
  }

// Получаем длину хэша
  DWORD  dwHashLen = 0;

  if(!CryptGetHashParam(
    hHash, 
    HP_HASHVAL, 
    NULL, 
    &dwHashLen, 
    0)) 
  {
    printf("Ошибка в получении длины хеша.\n");
    exit(1);
  }

// Получаем значение хэша
  BYTE *pbHash=new BYTE[dwHashLen];

  if(CryptGetHashParam(
    hHash, 
    HP_HASHVAL, 
    pbHash, 
    &dwHashLen, 
    0)) 
  {
    for(DWORD i = 0 ; i < dwHashLen ; i++) 
      printf("%2.2x",pbHash[i]);
    printf("\n");
  }
  else
  {
    printf("Ошибка при получении значения хеша.\n");
    exit(1);
  }

  if(pbHash)
    delete pbHash;

// Верифицируем сообщение
  if(CryptVerifySignature(
    hHash,          // дескриптор на hash объект
    pbArray,        // указатель на подпись
    cbArray,        // длина подписи
    hPubKey,        // дескриптор на public key
    NULL, 
    0)) 
  {
    printf("Сообщение верифицировано.\n");
  }
  else
  {
    HandleError("Ошибка CryptVerifySignature .");
  }

// Освобождаем память
  if(pbArray)
    delete pbArray;

// Уничтожаем дескрипторы
  if(hHash) 
    CryptDestroyHash(hHash);

  if(hPubKey) 
    CryptDestroyKey(hPubKey);

  if(hProv) 
    CryptReleaseContext(hProv, 0);

  if(pSignerCert)
    CertFreeCertificateContext(pSignerCert);

  if(CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))
  {
    printf("Хранилище закрыто. \n");
  }
  else
  {
    printf("Ошибка!");
  }

  return 0;
}


void HandleError(char *s)
{
    printf("Ошибка. \n");
    printf("%s\n",s);
    printf("Ошибка N %x.\n", GetLastError());
    printf("Программа завершена с ошибкой. \n");
    exit(1);
} 

int hex2int(char ch) throw(...)
{
  if (ch >= '0' && ch <= '9') return ch - '0';
  if (ch >= 'a' && ch <= 'f') return ch - 'a' + 0xA;
  if (ch >= 'A' && ch <= 'F') return ch - 'A' + 0xA;
  throw 1;    
}

int hex2bin(const char* hexsign, BYTE* pbarray)
{
  try{
    while(*hexsign)
    {
      int aa = hex2int(*hexsign++); 
      int bb = hex2int(*hexsign++);
      *pbarray++ = (BYTE)(aa << 4 | bb); 
    }
  }
  catch(...) {
    return -1;
  }
  return 0;
}

Результат должен быть следующим:

Сертификат найден.
CryptAcquireCertificatePrivateKey выполнилась успешно!
Имя CSP: Microsoft Base Cryptographic Provider v1.0
Имя key container: {B43505F2-9EA1-40E2-8398-08A160E23602}
Импортирован public key.
Hash объект создан.
Hash объект вычислен: ee7800b437a87c089ce049f0b316f523
Сообщение верифицировано.
Хранилище закрыто.

После верификации двух цифровых подписей рассмотрим алгоритм их создания.

Cоздание цифровой подписи в формате PKSC#7

Сначала создадим цифровую подпись в формате PKSC#7. Для создания этой цифровой подписи нужно установить сертификат, файл evgeny.pfx, содержащий пару закрытый/открытый ключ. Двойным щелчком запускаем certificate import wizard, при запросе пароля ничего не вводим, нажимаем кнопку Next. После установки сертификата можно создавать подпись.

Мы должны получить цифровую подпись, приведённую в начале статьи.

Последовательность действий:

// Создание цифровой подписи в формате PKSC#7
// Необходимо подключить библиотеку crypt32.lib
// Компиляция с использованием Visual Studio 7.0
#include <stdio.h>
#include <stdlib.h> // exit(1)

#include <wtypes.h>
#include <wincrypt.h>

#define MY_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

// Наименование персонального хранилища
#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( "Сертификат не найден.");
  }


// Переменные для указателя и длины подписи 
  BYTE  *pbSignedMessageBlob;
  DWORD cbSignedMessageBlob;

// Создаем и заполняем структуру для создания цифроовой подписи
  CRYPT_SIGN_MESSAGE_PARA  SigParams;

  SigParams.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);
  SigParams.dwMsgEncodingType = MY_TYPE;
  SigParams.pSigningCert = pSignerCert;
  SigParams.HashAlgorithm.pszObjId = szOID_RSA_MD5;
  SigParams.HashAlgorithm.Parameters.cbData = NULL;
  SigParams.cMsgCert = 0;
  SigParams.rgpMsgCert = NULL;
  SigParams.cAuthAttr = 0;
  SigParams.dwInnerContentType = 0;
  SigParams.cMsgCrl = 0;
  SigParams.cUnauthAttr = 0;
  SigParams.dwFlags = 0;
  SigParams.pvHashAuxInfo = NULL;
  SigParams.rgAuthAttr = NULL;


  const BYTE* MessageArray[] = {pbMessage};
  DWORD MessageSizeArray[1];
  MessageSizeArray[0] = cbMessage;

// Получаем длину буфера подписи

  if(CryptSignMessage(
    &SigParams,              // указатель на SigParams
    TRUE,                    // подпись создается отдельно 
    1,                       // число сообщений
    MessageArray,            // сообщение
    MessageSizeArray,        // длина сообщения
    NULL,                    // буфер для подписи
    &cbSignedMessageBlob))   // размер буфера
  {
    printf("Размер подписи %d.\n",cbSignedMessageBlob);
  }
  else
  {
    HandleError("Ошибка CryptSignMessage.");
  }

// выделяем память под подпись
  if(!(pbSignedMessageBlob = new BYTE[cbSignedMessageBlob]))
  {
    HandleError("Ошибка.");
  }

// формируем подпись
  if(CryptSignMessage(
    &SigParams,            // указатель на SigParams
    TRUE,                  // подпись создается отдельно 
    1,                     // число сообщений
    MessageArray,          // сообщение
    MessageSizeArray,      // длина сообщения
    pbSignedMessageBlob,   // буфер для подписи
    &cbSignedMessageBlob)) // размер буфера
  {
    printf("Подпись: \n");
    print_signature(cbSignedMessageBlob, pbSignedMessageBlob);
  }
  else
  {
    HandleError("Ошибка");
  }

  if (pbSignedMessageBlob)
    delete pbSignedMessageBlob;

  if(pSignerCert)
    CertFreeCertificateContext(pSignerCert);

  if(CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))
  {
    printf("\nХранилище закрыто. \n");
  }
  else
  {
    printf("Ошибка!");
  }

  return 0;
}

void print_signature(DWORD cbSigned, BYTE* pbSigned)
{
    for(DWORD i=0; i < cbSigned; i++) 
    {
      printf("%2.2x", pbSigned[i]);
      if ((i+1)%32 == 0) printf("\n");
    }
}

void HandleError(char *s)
{
    printf("Ошибка. \n");
    printf("%s\n",s);
    printf("Ошибка N %x.\n", GetLastError());
    printf("Программа завершена с ошибкой. \n");
    exit(1);
} 

Результат должен быть следующим:

Сертификат найден.
Размер подписи 370.
Подпись: 
3082016e06092a864886f70d010702a082015f3082015b020101310e300c0608
2a864886f70d02050500300b06092a864886f70d010701318201373082013302
010130818d307f311f301d06092a864886f70d010901161065766740696e746f
75726973742e7275310b3009060355040613025255310c300a06035504081303
4d4f53310f300d060355040713064d6f73636f7731123010060355040a130949
6e746f7572697374310c300a060355040b1303497664310e300c060355040313
054341457667020a615eeb5a00000000000a300c06082a864886f70d02050500
300d06092a864886f70d010101050004818071df37e9a80308355029533ddb70
5c9e5e8bd0b9c5e582d9623f134797a43c49bab00f25368d127f3870366b42dd
af71a0d5d475bfb44f9482ec918baf835d9a059e238b12c1945811b78423d8f2
11468c6c262099d604e527cefb22bec9a32ccd51624be3bd7684e502d212a6fc
1c2e6f132582df7833bcca9a44c8ad877366
Хранилище закрыто.

Подпись PKSC#7 такая же, что и в начале статьи.

Cоздание обычной цифровой подписи

Теперь создадим обычную подпись, используя наш сертификат (файл evgeny.pfx), размером 256 байт в hex-представлении. Как и в случае верификации такой подписи, воспользуемся функцией CryptAcquireCertificatePrivateKey. Она обеспечит переход от сертификата к соответствующему дескриптору CSP. Используя дескриптор, нам осталось получить подпись.

Ниже представлена последовательность действий.

CertOpenStore - открывает хранилище сертификатов.

CertFindCertificateInStore – находит нужный сертификат.

CryptAcquireCertificatePrivateKey –получает дескриптор CSP провайдера соответствующего нашему сертификату.

CryptCreateHash – создает хэш

CryptSignHash – шифрует созданный хэш

// Создание цифровой подписи 
// Необходимо подключить библиотеку crypt32.lib
// Компиляция с использованием Visual Studio 7.0

#include <stdio.h>
#include <stdlib.h> // exit(1)

#include <wtypes.h>
#include <wincrypt.h>

#define MY_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

// Наименование персонального хранилища
#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("Ошибка!");
  }

  return 0;
}

void print_signature(DWORD cbSigned, BYTE* pbSigned)
{
    for(DWORD i=0; i < cbSigned; i++) 
    {
      printf("%2.2x", pbSigned[i]);
      if ((i+1)%32 == 0) printf("\n");
    }
}
void HandleError(char *s)
{
    printf("Ошибка. \n");
    printf("%s\n",s);
    printf("Ошибка N %x.\n", GetLastError());
    printf("Программа завершена с ошибкой. \n");
    exit(1);
} 
 

Результат должен быть следующим:

Сертификат найден.
CryptAcquireCertificatePrivateKey выполнилась успешно!
Hash объект создан.
Hash объект вычислен.
Длина подписи 128 .
Память под подпись выделена.
Подпись:
667387adc8449acabc3378df8225136f2e1cfca612d202e58476bde34b6251cd
2ca3c9be22fbce27e504d69920266c8c4611f2d82384b7115894c1128b239e05
9a5d83af8b91ec82944fb4bf75d4d5a071afdd426b3670387f128d36250fb0ba
493ca49747133f62d982e5c5b9d08b5e9e5c70db3d532950350803a8e937df71
Хранилище закрыто.

И последнее. Если вы возьмете 256 последних байт из hex-представления цифровой подписи, сформированной в PKSC#7 формате, и прочитаете их в обратном порядке, вы получите подпись из последнего примера.


Эта статья опубликована в журнале RSDN Magazine #1. Информацию о журнале можно найти здесь
    Сообщений 6    Оценка 206        Оценить