Создание и верификация цифровой подписи в CryptoAPI через сертификат
открытого ключа
В рамках статьи рассматривается создание и проверка цифровой подписи с использованием алгоритма шифрования 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 |
Верификация цифровой подписи в формате 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-вид, поэтому при верификации ее нужно преобразовать обратно в текстовый формат.
Алгоритм верификации состоит из вызова следующих функций:
- CertOpenStore - открываем хранилище сертификатов.
- CertFindCertificateInStore – находим нужный сертификат.
- CRYPT_VERIFY_MESSAGE_PARA – заполняем структуру для верификации
- CryptVerifyDetachedMessageSignature – проверяем подпись.
// Верификация цифровой подписи, представленной в формате 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:
- Gemplus GemSAFE Card CSP v1.0
- Microsoft Base Cryptographic Provider v1.0
- Microsoft Base DSS and Diffie-Hellman Cryptographic Provider
- Microsoft Base DSS Cryptographic Provider
- Microsoft DH SChannel Cryptographic Provider
- Microsoft Enhanced Cryptographic Provider v1.0
- Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider
- Microsoft RSA SChannel Cryptographic Provider
- Microsoft Strong Cryptographic Provider
- Schlumberger Cryptographic Service Provider
Каждый CSP имеет свою базу ключей (key database), в которой находится один или несколько контейнеров (key containers), где хранятся пары закрытых/открытых ключей. При установке сертификата в операционную систему, в базе ключей соответствующего CSP (в нашем случае - Microsoft Base Cryptographic Provider v1.0) создается контейнер. Для проверки этой подписи, необходимо перейти к работе с CSP и контейнеру нашего сертификата через функцию CryptAcquireCertificatePrivateKey. В ходе верификации получим для справки имя CSP, уникальное имя контейнера ключей сертификата, а также значение MD5-хеша сообщения.
Процесс верификации заключается в вызове следующих функций:
- CertOpenStore - открывает хранилище сертификатов.
- CertFindCertificateInStore – находит нужный сертификат.
- CryptAcquireCertificatePrivateKey – получает дескриптор CSP провайдера соответствующего сертификата.
- CryptGetProvParam – получает имена CSP и контейнера ключей (для справки).
- CryptImportPublicKeyInfo – импортирует открытый ключ из сертификата.
- CryptCreateHash – создает хэш для нашего сообщения
- CryptGetHashParam – получает значение MD5 хэша (для справки).
- CryptVerifySignature – проверяет подпись.
// Верификация цифровой подписи без дополнительной информации // Необходимо подключить библиотеку 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. После установки сертификата можно создавать подпись.
Мы должны получить цифровую подпись, приведённую в начале статьи.
Последовательность действий:
- CertOpenStore - открывает хранилище сертификатов.
- CertFindCertificateInStore – находит нужный сертификат.
- CRYPT_SIGN_MESSAGE_PARA – заполнение структуры для создания подписи
- CryptSignMessage – создаем подпись.
// Создание цифровой подписи в формате 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 формате, и прочитаете их в обратном порядке, вы получите подпись из последнего примера.