23.01.2007 13:40:06При проверке ЭЦП получаю NTE_BAD_SIGNATURE Ответов: 11
Алексей
Добрый день!
Возникла проблема, вкратце описать которую не получается, поэтому опишу подробно :)
Необходимо проверять ЭЦП и подписывать документы (на одной и той же машине). Документы очень большого объема, сертификаты (в виде блобов) извлекаются из базы данных, в системе установлен КриптоПро.
Реализован примерно такой алгоритм:

ПРИ ПОДПИСИ
CryptAcquireContext(&csp,сontainerName, cspName, DWPROVTYPE, 0) );
....
CryptCreateHash(csp, HASHFLAG, 0, 0, &hash);
.... (в цикле вычисляем хеш для кусков документа)
for (int i=0; i<iCycles; ++i) {
....
CryptHashData(hash, message, size, 0);
...
}
....
CryptSignHash(hash, KEYSPEC, NULL, 0, NULL, &cbSignature);
....
CryptSignHash(hash, KEYSPEC, NULL, 0, signature, &cbSignature);

ПРИ ПРОВЕРКЕ
CryptAcquireContext(&csp, сontainerName, cspName, DWPROVTYPE, 0);
....
CryptImportPublicKeyInfo(csp, MY_ENCODING_TYPE, &(pContext->pCertInfo->SubjectPublicKeyInfo), &pubKey)
....
CryptCreateHash(csp, HASHFLAG, 0, 0, &hash);
.... (в цикле вычисляем хеш для кусков документа)
for (int i=0; i<iCycles; ++i) {
....
CryptHashData(hash, message, size, 0);
...
}
....
CryptVerifySignature(hash, sign, cbSign, pubKey, NULL, 0);

При использовании Microsoft Enhanced Cryptographic Provider v1.0 и подписи хеша с флагом AT_SIGNATURE все в порядке.
Как только перехожу на Crypto-Pro GOST R 34.10-94 Cryptographic Service Provider и AT_KEYEXCHANGE функция CryptVerifySignature возвращает NTE_BAD_SIGNATURE. Документы точно приходят одни и те же (при подписывании/проверке), никаких нарушений в структуре ЭЦП тоже не происходит, на вход проверке подается ровно то, что получилось на выходе подписывания.
Что я не учитываю, не понимаю, делаю не так?
 
Ответы:
23.01.2007 15:37:11Kirill Sobolev
проверка - вместо CryptAcquireContext(&csp, сontainerName, cspName, DWPROVTYPE, 0), попробуйте CryptAcquireContext(&csp, NULL, cspName, DWPROVTYPE, CRYPT_VERIFYCONTEXT)
23.01.2007 15:48:30Алексей
Кирилл, спасибо за совет.
Попробовал, не помогло :)
Хотя, конечно, флаг CRYPT_VERIFYCONTEXT и должен там находится по логике вещей.

Скачал примеры (исходники) с этого сайта и возник еще один вопрос.

В программе определяется параметр HP_OID, выделяется для него память, в хеш (почему-то функцией CryptGetHashParam а не CryptSetHashParam) передается эта память.

Что это? Может, именно из-за "неопределения" этого параметра мои подписи некорректны?
23.01.2007 16:21:53Kirill Sobolev
Вы csptest скачали? Где именно там определяется параметр HP_OID?
23.01.2007 16:50:17Алексей
Файл SigningHash.c
Куски кода:

При подписывании
/--------------------------------------------------------------------
// Определение размера BLOBа и распределение памяти.

if(CryptGetHashParam(hHash,
HP_OID,
NULL,
&cbHash,
0))
{
printf("Size of the BLOB determined. \n");
}
else
{
HandleError("Error computing BLOB length.");
}

pbHash = (BYTE*)malloc(cbHash);

if(pbHash)
{
printf("Memory has been allocated for the pbHash. \n");
}
else
{
HandleError("Out of memory. \n");
}


//--------------------------------------------------------------------
// Копирование параметра HP_OID в pbHash.

if(CryptGetHashParam(hHash,
HP_OID,
pbHash,
&cbHash,
0))
{
printf("Parameters have been written to the pbHash. \n");
}
else
{
HandleError("Error during CryptGetHashParam.");
}




При проверке
//--------------------------------------------------------------------
// Установка параметра HP_OID.

// По умолчанию провайдер работает на наборе параметров 1.2.643.2.2.30.1.
// Без установки параметра HP_OID программа будет неверно работать,
// если параметры хеширования не будут являться параметрами
// по умолчанию.

if(CryptSetHashParam(
hHash,
HP_OID,
pbHash,
0))
{
printf("The parameters have been set. \n");
}
else
{
HandleError("Error during SetHashParam.");
}
23.01.2007 17:08:27Алексей
Ага, просто невнимательно посмотрел с самого начала.
Это просто параметры хеша передаются для корректной проверки.

Однако, вопрос остается открытым: что я делаю не так, почему NTE_BAD_SIGNATURE?

Добавлю - только что попробовал получать контекст криптопровайдера (при подписывании) через CryptAcquireCertificatePrivateKey(pContext, 0,
NULL, &csp, &dwKeySpec, &fCallerFreeProv);
Получил ошибку CRYPT_E_NO_KEY_PROPERTY
Ерунда какая-то.
Сертификат точно существует и связан с ключами, он вообще единственный.
23.01.2007 17:59:46Василий
Видимо, для начала неплохо бы убедиться в том, что а) вычисленные хеш-значения при подписи и при проверке подписи совпадают, б) при экспорте ключа подписи в PUBLICKEYBLOB его значение совпадает с тем, что в сертификате.
23.01.2007 22:44:32Алексей
Василий!

> вычисленные хеш-значения при подписи и при проверке подписи совпадают

Проверить не знаю как, но они совпадают (почему пришел к такому выводу объясню ниже)

> при экспорте ключа подписи в PUBLICKEYBLOB его значение совпадает с тем, что в сертификате


Вообще я изначально не планировал экспортировать открытый ключ.
Мне виделось, что вместе с ЭЦП лучше хранить отпечаток сертификата (20 байт), а не открытый ключ, соответствующий подписи (100 байт). По отпечатку я хочу получить сертификат и извлечь из него ключ посредством CryptImportPublicKeyInfo (я правильно рассуждаю?).
НО!
После того, как я экспортировал ключ при подписи (CryptExportKey) и при проверке импортировал его (CryptImportKey) - все заработало, проверка проходит успешно (на основании чего я делаю вывод что хеши при подписании и проверке совпадают).
То есть CryptImportPublicKeyInfo импортирует открытый ключ, не соответствующий закрытому ключу, который использовался при подписывании?
Почему так? Почему на майкрософтовских провайдерах такой ситуации не встречалось? И как мне получить "соответствующий" ключ с помощью CryptImportPublicKeyInfo ?

Спасибо.
24.01.2007 16:21:17Василий
Что-то тут не так.
Настораживает вот что:
> CryptAcquireCertificatePrivateKey(pContext, 0,
NULL, &csp, &dwKeySpec, &fCallerFreeProv);
> Получил ошибку CRYPT_E_NO_KEY_PROPERTY
Если сертификат берётся из хранилища, и есть ссылка на ключевой контейнер, то контейнер должен был открыться.
Может, сертификат не тот или ключ не тот (AT_SIGNATURE или AT_KEYEXCHANGE)?
Попробуйте сохранить этот сертификат в файл и установить его через панель CSP - Сервис - "Установить личный сертификат" - он заодно проверит соответствие закрытого и открытого ключей
24.01.2007 17:33:26Алексей
Сертификат не сохраняется, если ставить галочку "Экспортировать закрытый" ключ. Сразу хочу отметить, что сертификат 1) тестовый, 2) с истекшим сроком действия.
При этом пишет "экспорт выполнен успешно", но сам файл не создается.
Если не сохранять без закрытого ключа, то все дальнейшие действия проходят успешно. Во всяком случае ошибок нигде никто не выдает :)
25.01.2007 11:43:23Василий
Сертификат ГОСТ нельзя сохранить в файл pfx с закрытым ключом. Это сделано специально, и неоднократно упоминалось на данном форуме.
25.01.2007 11:47:49Kirill Sobolev
"То есть CryptImportPublicKeyInfo импортирует открытый ключ, не соответствующий закрытому ключу, который использовался при подписывании?" CryptImportPublicKeyInfo никак с закрытым ключем не связана и импортирует тот ОК, который Вы ей дадите.