| ||||
| ||||
Доброе утро! ЧТо за вилы с этим Crypto API? Никак не могу получить открытый ключ из контейнера, чего только не делал! Исходник : DWORD namesize; DWORD s; char provName[255]; int i = 0; while (CryptEnumProviders(i, 0, 0, &s, 0, &namesize)) { if (CryptEnumProviders(i, 0, 0, &s, provName, &namesize)) { HCRYPTPROV hProv; if (!CryptAcquireContext(&hProv, NULL, provName, s, CRYPT_VERIFYCONTEXT)) return 0; DWORD size; BYTE result[255]; DWORD fParam = CRYPT_FIRST; std::cout << provName << std::endl; while ( CryptGetProvParam(hProv, PP_ENUMCONTAINERS, NULL, &size, fParam) ) { memset(&result,0,255); CryptGetProvParam(hProv, PP_ENUMCONTAINERS, result, &size, fParam); fParam = 0; std::cout << result << std::endl; HCRYPTPROV tmpProv = NULL; HCRYPTKEY hPublicKey = NULL; if (CryptAcquireContext(&tmpProv, (LPCSTR)result, provName, s, CRYPT_VERIFYCONTEXT)) { if (CryptGetUserKey(htmpProv, AT_SIGNATURE | AT_KEYEXCHANGE, &hPublicKey)) std::cout << "Îêòðûòûé êëþ÷ ïîëó÷åí" << std::endl; else ErrMsg(); CryptReleaseContext(tmpProv, 0); } else ErrMsg(); } std::cout << "--------------------------------------------------------" << std::endl; CryptReleaseContext(hProv, 0); } i++; } 1. Получаею Имя и Тип криптопровайдера (ВСЕ ОК) 2. Получаю контекст криптопровайдера (ВСЕ ОК) 3. Получаю имя контейнера (Тоже ВСЕ ОК) 4. Получаю контекст tmp криптопровайдера (используя полученные тип, имя контейнера и имя криптопровайдера) и Вот тут Все время ошибка : Указаны неправильные флаги. Пробовал без повторного получения контекста криптопровайдера в tmp, получать октрытый ключ сразу после определения имени контейнера, нифига не выходит, орет уже на функцию CryptGetUserKey, что типа Ключи не найдены! Ну что за лажа? ДУмал, что ключей правда нет в контейнере, попробовал сгенерировать пару, ругается что ключи в контейнере уже есть, а ключи действительно есть, при просмотре сертификата средствами CryptoPRO CSP, открытый ключик лежит в виде hex строчки... Вот как его выцерапать из контейнера? Ведь открытый то ключ должен без проблем получаться? | ||||
Ответы: | ||||
| ||||
CRYPT_VERIFYCONTEXT лишний. When this flag is set, the application has no access to the persisted private keys of public/private key pairs, and the pszContainer parameter must be set to NULL. | ||||
| ||||
Если не трудно подскажите какой флаг указывать при получении контекста? | ||||
| ||||
Хм.. Странно... Выставил флаг в 0, ругается что мол вставьте ключевой носитель для пользователя такого-то... Ключи не найдены! | ||||
| ||||
0 и надо. Видимо контейнер находится на съемном носителе, поэтому CSP и просит его вставить. | ||||
| ||||
Да нет в реестре. Смотрю руками: Control Panel->CryptoPRO CSP->Сервис->Просмотреть сертификаты в контейнере->Выбираю контейнер (считыватель реестр)->Сертификат для просмотра->Свойства->Состав->и там в списке есть такая строчка Открытый ключ ГОСТ Р 34.10-94 (1024 bits) А также Использование ключа : Цифровая подпись, неотрекаемость и т.п. Отпечаток - hex строка.. То есть к чему я, ключик по всей видимости есть в контейнере, но вот достучаться до него не могу, также смотрел в реестре, там тоже есть HKEY_LOCAL_MACHINE\SOFTWARE\Crypto Pro\Settings\USERS\S-1-5-21-1177238915-1606980848-1060284298-6016\Keys\ Имя контейнера\ в нем 6 ключиков лежит... программно нифига не получается, постоянно требует вставить ключевой носитель Реестр | ||||
| ||||
Правда у меня демострационный CryptoPro CSP (30 дней) может из-за этого? | ||||
| ||||
И 30 дней прошло уже? | ||||
| ||||
Неа... Еще 21 день остался.. | ||||
| ||||
А "Имя контейнера" Вы в CryptAcquireContext указываете? Возможно, что для пользователя нет контейнера по умолчанию. | ||||
| ||||
Указываю... Самое интересное тогда получает контекст, но когда вызываю CryptGetUserKey, ругается что ключи не найдены! =( | ||||
| ||||
А там есть ключ обмена? | ||||
| ||||
Извиняюсь за ламерство (пока плохо разбераюсь в теме!), а что такое ключ обмена? Как такового в списке нет! | ||||
| ||||
Ключ обмена - это ключ, который позволяет обмениваться шифрованными сообщениями. Вы же вызываете CryptGetUserKey с AT_KEYEXCHANGE, это означает что пытаетесь достать именно ключ обмена. | ||||
| ||||
А для того, чтобы достать открытый ключик для проверки цифровой подписи, мне нужно юзать AT_SIGNATURE? | ||||
| ||||
В контейнере может быть любой из ключей AT_xxx, а может быть и оба. AT_SIGNATURE можно использовать только для подписи. AT_KEYEXCHANGE - и для шифрования и для подписи. | ||||
| ||||
А ключ для проверки подписи обычно берут из сертификата. | ||||
| ||||
Ага, вот уже теплее стало, хоть что-то... Главное теперь разобраться как до сертификата достучаться и ключик получить! =) | ||||
| ||||
Сертификат должен быть доступен на машине, где подпись проверятся. Хранить его можно, например, в хранилище или в файле. | ||||
| ||||
Сертификат я получил =) Теперь разобраться бы чего дальше делать! =) HCERTSTORE hStoreHandle; if ( !( hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, //CERT_SYSTEM_STORE_LOCAL_MACHINE, L"MY"))) { printf("Can not open Store MY.\n"); return 0; } PCCERT_CONTEXT pSignerCert = NULL; if(pSignerCert = CertFindCertificateInStore(hStoreHandle,PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, L"test", NULL)) { printf("Certificates Found %s\n"); } else printf( "Certificates not Found.\n"); if(pSignerCert) CertFreeCertificateContext(pSignerCert); CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG); | ||||
| ||||
А какая глобальная задача стоит? | ||||
| ||||
на самом деле все просто! Мне нужно проверить ЭЦП у полученного документа и все! Документ в формате XML, в одном из тегов сообщения лежит ЭЦП, мне нужно проверить, что документ не изменился в процессе передачи! То есть проверить ЭЦП полученного сообщения! | ||||
| ||||
CryptoAPI не работает с XML, может быть проще воспользоваться тем API которым этот XML подписывался? | ||||
| ||||
Да не.. Вы меня не поняли! =) XML я парсю сам, программно! Я знаю в каком теге лежит ЭЦП. Суть вот в чем : 1. Взять ЭЦП1 из XML сообщения (Я беру её без проблем) 2. Создать своё ЭЦП2 (Хеш нужных полей + Открытый ключ из сертификата) 3. Сравнить ЭЦП1 и ЭЦП2 Вот и все! Сертификат я получил, теперь не совсем понятно как из него выцерапать открытый ключ, ну и создать Хэш значение.. | ||||
| ||||
А ЭЦП1 как получается? | ||||
| ||||
> 1. Взять ЭЦП1 из XML сообщения (Я беру её без проблем) > 2. Создать своё ЭЦП2 (Хеш нужных полей + Открытый ключ из сертификата) > 3. Сравнить ЭЦП1 и ЭЦП2 При создании ЭЦП одних и тех же данных каждый раз будет разный результат, т.к. в процессе участвует случайное число. | ||||
| ||||
Блин! Гемморой... Как тогда быть? Как аутентифициаровать документ? Проверить, что он не был изменен? | ||||
| ||||
При проверке подписи не нужен закрытый ключ. Если при подписывании используется сертификат, то для проверки можно использовать открытый ключ из сертификата. Если не используется - можно просто добавить в документ поле, в которое засунуть экспортированный в блоб открытый ключ подписавшего. | ||||
| ||||
ТАк мне закрытый ключ и не нужен, яж писал выше, открытый ключик мне получить надо! Кста, а вот ЭЦП в каждом случае (для каждого документа) будет разное, потому что при формировании ЭЦП используется хэш значение определенных полей и открый ключ! Блин запутался совсем! Тогда как мне понять был изменен документ или нет? | ||||
| ||||
При создании подписи каждый раз будет получаться разное значение ЭЦП. При проверке подписи используются: - хеш документа - открытый ключ подписавшего - значение ЭЦП (оно не вычисляется, а берётся готовое) Если документ изменится, то изменится его хеш и подпись станет неверной. | ||||
| ||||
Вроде прочухал... =) Теперь нужно грамотно реализовать сие! Есть пробелма еще в том что у меня стоит VC++ 6 А в хидере wincrypt.h нет прототипа функции CryptAcquireCertificatePrivateKey (для получения криптопровайдера связано с нужным сертификатом), криптопровайдер нужно тогда получать получать другой функцией, отсюда вопрос : Как оперделить что это именно тот криптопровайдер и сертификат соответствует ему! Кстати, а хэш вычисляется всегда по одному алгоритму или в КриптоПро свой алгоритм формирования хэша? | ||||
| ||||
Почитал в MSDN что вроде как хэш создается с использованием CALG_MD5... КриптоПро тоже использует этот метод создания хэша, или у него другой (свой)? | ||||
| ||||
Есть предложение формировать подпись в формате PKCS7 и переложить всю увлекательную работу по инициализации провайдера на CryptoAPI | ||||
| ||||
Алгоритм хеша: ГОСТ P 34.11-94 А вот CryptAcquireCertificatePrivateKey стопудово не нужна при проверке подписи. Вообще, вопрос - вычисление ЭЦП (на стороне отправителя) тоже будет делать Ваша программа? Если да, посмотрите наш пример SigningHash из http://www.cryptopro.ru/cryptopro/download/60763684-1BBD-4A65-B475-A2D86A7CDD24/30/3293/sdk.zip | ||||
| ||||
Нет, на стороне клиента работает не мой код, но у них стоит наш сертификат ну а софт CryptoPro 2.0 (криптография ГОСТЫ реализованные в CryptoPro 2.0) У меня криптографические библиотеко аналогичные стоят стоит, и сертификат тоже такой же как у клиента, они подписывают, мне только верифицировать нужно и все... | ||||
| ||||
Вроде написал, сначала создаю подпись, потом её верифицирую, тока своль ругается, что мол Неправильная подпись.. Пока не вчухал почему... Вот исходничек если интересно: #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0400 #endif #include <windows.h> #include <stdio.h> #include <iostream> #include <wincrypt.h> #pragma comment (lib, "Crypt32.lib") #pragma comment (lib, "Advapi32.lib") //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- BYTE *ecp; DWORD ecplen; //------------------------------------------------------------------------------------- BYTE* pbMessage = (BYTE*)"Hello world"; DWORD cbMessage = (DWORD)strlen((char*) pbMessage)+1; //------------------------------------------------------------------------------------- DWORD AlgID; //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- void ErrMsg() { char err_message[1000]; DWORD err = GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) err_message, sizeof(err_message), NULL ); sprintf(err_message,"%s - %d",err_message,err); CharToOem(err_message,err_message); std::cout << err_message << std::endl; std::cout << "-----------------------" << std::endl; } //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- 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"); } } //------------------------------------------------------------------------------------- // Ñîçäàåì ïîïèñü //------------------------------------------------------------------------------------- bool CreateECP() { // Îòêðûâàåì õðàíèëèùå ñåðòèôèêàòîâ HCERTSTORE hStoreHandle; if (!( hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL,CERT_SYSTEM_STORE_CURRENT_USER, L"MY"))) { ErrMsg(); return 0; } // Ïîëó÷àåì óêàçàòåëü íà íàø ñåðòèôèêàò PCCERT_CONTEXT pSignerCert; if (pSignerCert = CertFindCertificateInStore(hStoreHandle,PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,0, CERT_FIND_SUBJECT_STR, L"test2",NULL)) printf("Certificate found...\n"); else ErrMsg(); // Ïåðåìåííûå äëÿ óêàçàòåëÿ è äëèíû ïîäïèñè BYTE *pbSignedMessageBlob; DWORD cbSignedMessageBlob; // Ñîçäàåì è çàïîëíÿåì ñòðóêòóðó äëÿ ñîçäàíèÿ öèôðîîâîé ïîäïèñè CRYPT_SIGN_MESSAGE_PARA SigParams; SigParams.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA); SigParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING; SigParams.pSigningCert = pSignerCert; SigParams.HashAlgorithm.pszObjId = pSignerCert->pCertInfo->SignatureAlgorithm.pszObjId; AlgID = CertOIDToAlgId(pSignerCert->pCertInfo->SignatureAlgorithm.pszObjId); 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("ECP Size %d.\n",cbSignedMessageBlob); else ErrMsg(); // âûäåëÿåì ïàìÿòü ïîä ïîäïèñü if(!(pbSignedMessageBlob = new BYTE[cbSignedMessageBlob])) ErrMsg(); // ôîðìèðóåì ïîäïèñü if(CryptSignMessage(&SigParams, // óêàçàòåëü íà SigParams TRUE, // ïîäïèñü ñîçäàåòñÿ îòäåëüíî 1, // ÷èñëî ñîîáùåíèé MessageArray, // ñîîáùåíèå MessageSizeArray, // äëèíà ñîîáùåíèÿ pbSignedMessageBlob, // áóôåð äëÿ ïîäïèñè &cbSignedMessageBlob)) // ðàçìåð áóôåðà { printf("ECP:\n"); print_signature(cbSignedMessageBlob, pbSignedMessageBlob); ecp = new BYTE[cbSignedMessageBlob]; memset(ecp,0,cbSignedMessageBlob); memcpy(ecp,pbSignedMessageBlob,cbSignedMessageBlob); ecplen = cbSignedMessageBlob; } else ErrMsg(); if (pbSignedMessageBlob) delete pbSignedMessageBlob; if(pSignerCert) CertFreeCertificateContext(pSignerCert); if(!CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG)) ErrMsg(); return 0; } //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- // Ïðîâåðÿåì ïîäïèñü //------------------------------------------------------------------------------------- bool VerifyECP() { bool fl = false; HCERTSTORE hStoreHandle; if ( !( hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"))) { ErrMsg(); return 0; } // Ïîëó÷àåì óêàçàòåëü íà íàø ñåðòèôèêàò PCCERT_CONTEXT pSignerCert = NULL; if(pSignerCert = CertFindCertificateInStore(hStoreHandle,PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, L"test2", NULL)) printf("Certificates Found\n"); else ErrMsg(); if(pSignerCert) { HCRYPTKEY hKey = NULL; CERT_PUBLIC_KEY_INFO keyInfo = pSignerCert->pCertInfo->SubjectPublicKeyInfo; DWORD namesize; DWORD s; char provName[255]; int i = 0; HCRYPTPROV hProv = NULL; while (CryptEnumProviders(i, 0, 0, &s, 0, &namesize)) if (CryptEnumProviders(i, 0, 0, &s, provName, &namesize)) if (CryptAcquireContext(&hProv, "bspb_test", provName, s, 0)) break; else i++; if (CryptImportPublicKeyInfo(hProv,PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, &keyInfo, &hKey)) { // Ñîçäàåì ïóñòîé hash îáúåêò HCRYPTHASH hHash; if(CryptCreateHash(hProv, AlgID, 0, 0, &hHash)) printf("Hash Object Created.\n"); else ErrMsg(); //-------------------------------------------------------------------- // Âû÷èñëÿåì hash äëÿ íàøåãî ñîîáùåíèÿ if(CryptHashData(hHash, pbMessage,cbMessage, 0)) printf("Hash Object Calculate: \n"); else ErrMsg(); // Âåðèôèöèðóåì ñîîáùåíèå if(CryptVerifySignature(hHash, // äåñêðèïòîð íà hash îáúåêò ecp, // óêàçàòåëü íà ïîäïèñü ecplen, // äëèíà ïîäïèñè hKey, // äåñêðèïòîð íà public key NULL,0)) { printf("ECP Right...\n"); fl = true; } else ErrMsg(); // Îñâîáîæäàåì ïàìÿòü // Óíè÷òîæàåì äåñêðèïòîðû if(hHash) CryptDestroyHash(hHash); if(hKey) CryptDestroyKey(hKey); if(hProv) CryptReleaseContext(hProv, 0); } CertFreeCertificateContext(pSignerCert); } CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG); return fl; } //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- int main(int carg,LPSTR* args) { ecp = NULL; CreateECP(); printf("\n\n\n"); VerifyECP(); if (ecp) delete ecp; return 0; } | ||||
| ||||
Странная идея - подписывать высокоуровневой функцией (CryptSignMessage), а проверять низкоуровневыми. Вы уж определитесь... | ||||
| ||||
А в чем проблема то? Почему нельзя проверять низкоуровневой? | ||||
| ||||
Дайте пример подписи и проверки подписи который будет работать на VC++ 6 | ||||
| ||||
> Почему нельзя проверять низкоуровневой? Потому что высокоуровневая функция подписывает не сам открытый текст, а преобразовывает его. Если вы знаете, как она это делает - тогда вперёд. Только вот - зачем? Тем более, что, по вашим словам, вам нужно только проверять подпись... Готового примера для VC6 нет - очень уж старинная среда. Возьмите исходник по ссылке, что я прислал - он практически годится, с небольшими изменениями в части работы с сертификатами. Схема: создание подписи: - по сертификату получить хендл контейнера ключа (это есть у вас) - посчитать хеш (это есть у вас в ф-и проверки) - вычислить ЭЦП ф-ей CryptSignHash проверка подписи: - из сертификата получить хендл открытого ключа (это есть у вас) - посчитать хеш (это есть у вас) - проверить ЭЦП ф-ей CryptVerifySignature (это есть у вас) | ||||
| ||||
Ура! Заработало.... | ||||