Windows CryptoAPI: CryptSignHash с CALG_SHA_256 и закрытым ключом из МОЕГО хранилища ключей

Я пытаюсь создать цифровые подписи в Windows (из XP SP3, но в настоящее время тестирую Windows 7) с помощью CryptoAPI, который будет совместим со следующими командами openssl:

openssl dgst -sha256 -sign <parameters> (for signing)
openssl dgst -sha256 -verify <parameters> (for validation)

Я хочу использовать закрытый ключ из хранилища ключей Windows «MY» для подписи.

Мне удалось подписать файлы, используя алгоритм дайджеста SHA1, используя следующие функции CryptoAPI (для краткости опуская параметры):

CertOpenStore
CertFindCertificateInStore
CryptAcquireCertificatePrivateKey
CryptCreateHash (with CALG_SHA1)
CryptHashData
CryptSignHash

Сгенерированная подпись совместима с «openssl dgst -sha1 -verify» (после изменения порядка байтов).

Моя проблема: когда я пытаюсь использовать CALG_SHA_256 с CryptCreateHash, происходит сбой с ошибкой 80090008 (NTE_BAD_ALGID). Погуглив , я обнаружил, что мне нужно использовать определенный поставщик (PROV_RSA_AES) вместо поставщика по умолчанию. Поскольку у меня будет дескриптор поставщика, мне также потребуется заменить CryptAcquireCertificatePrivateKey на CryptGetUserKey. Поэтому я изменил свою программу, чтобы она выглядела так:

CryptAcquireContext (with PROV_RSA_AES)
CertOpenStore
CertFindCertificateInStore
CryptGetUserKey
CryptCreateHash (with CALG_SHA256)
CryptHashData
CryptSignHash

К сожалению, это не сработало, как ожидалось: CryptGetUserKey завершился с ошибкой 8009000D (NTE_NO_KEY). Если я удаляю вызов CryptGetUserKey, программа работает до CryptSignHash, что приводит к ошибке 80090016 (NTE_BAD_KEYSET). Я знаю, что набор ключей существует и отлично работает, так как я смог использовать его для подписи дайджеста SHA1.

Я попытался снова получить контекст с информацией из контекста сертификата, полученного из CertFindCertificateInStore: лучшее, что я мог сделать, это успешный вызов CryptGetUserKey, но CryptSignHash всегда завершался ошибкой с той же ошибкой.

Закрытый ключ, который я пытаюсь использовать, имеет длину 2048 бит, но я не ожидаю, что это будет проблемой, поскольку он работает с дайджестом SHA1. Я в недоумении, поэтому любое предложение будет очень кстати!


person Dominique Eav    schedule 16.11.2010    source источник
comment
как вы изменили порядок байтов. это было 128-битное обращение   -  person ashmish2    schedule 20.04.2011


Ответы (5)


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

Вероятно, вам нужно открыть связанный контекст для сертификата (посмотрите CertGetCertificateContextProperty с параметром CERT_KEY_PROV_HANDLE_PROP_ID). С помощью этого дескриптора вы можете попробовать экспортировать ключ из исходного контекста поставщика и повторно импортировать его в новый контекст PROV_RSA_AES (при условии, что ключ можно экспортировать).

person tyranid    schedule 16.11.2010
comment
Спасибо за быстрый ответ! Это скорее всего моя проблема. К сожалению, я не могу предположить, что ключ можно экспортировать, поэтому мне нужно решить мою проблему по-другому. - person Dominique Eav; 16.11.2010
comment
@Dominique Eav Вы нашли решение? если ключ не экспортируется, у меня та же проблема... - person Cobaia; 28.02.2012
comment
@Cobaia: если ключ нельзя экспортировать... то, боюсь, это невозможно. - person Dominique Eav; 01.03.2012

80090008 вызвано тем, что базовый провайдер не поддерживает SHA256, SHA384 и SHA512, вы должны использовать CryptAcquireContext(hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0);

person Delphi    schedule 24.04.2012

В большинстве предыдущих ответов есть части реального ответа. Способ сделать это — получить HCRYPTPROV, который использует контейнер ключа и «Microsoft Enhanced RSA and AES Cryptographic Provider», позвонив

CryptAcquireContext(&hCryptProv, <keyContainerName>, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_SILENT)

Полученный hCryptProv можно использовать для подписи хэшей, созданных всеми поддерживаемыми SHA2: 256, 384, 512.

Имя контейнера ключей можно получить либо с помощью CertGetCertificateContextProperty() с аргументом CERT_KEY_PROV_INFO_PROP_ID, если ключ получен через сертификат, либо с помощью CryptGetProvParam() с аргументом PP_CONTAINER, если для этого ключа уже существует HCRPYPTPROV (например, полученный с помощью CryptAcquireCertificatePrivateKey).

Этот метод работает, даже если закрытый ключ нельзя экспортировать.

person 255    schedule 20.10.2015

После того, как вы нашли сертификат, попробуйте вызвать CertGetCertificateContextProperty с помощью CERT_KEY_PROV_INFO_PROP_ID, чтобы получить имя провайдера и контейнера с ключом. Попробуйте вызвать CryptAcquireContext с этими именами, но указав PROV_RSA_AES в качестве типа провайдера.

Вы также можете попробовать заменить имя провайдера на «Microsoft Enhanced RSA and AES Cryptographic Provider», но это точно не сработает, если провайдер не является одним из других провайдеров Microsoft.

person Rasmus Faber    schedule 16.11.2010
comment
Я хотел бы попробовать ваше предложение, но я не могу найти способ передать массив хэш-байтов непосредственно в CryptSignHash: для этой функции требуется хэш-дескриптор (HCRYPTHASH). Думаю, я могу использовать CryptCreateHash и инициализировать хеш с помощью массива байтов, но если хеш-алгоритм не поддерживается провайдером, я далеко не уйду. - person Dominique Eav; 16.11.2010
comment
Извините, вы правы. Я не думаю, что вы можете сделать это таким образом. Я напишу другое предложение, вы можете попробовать. - person Rasmus Faber; 16.11.2010
comment
Я пробовал это и пробовал снова после того, как вы обновили свой ответ, но, похоже, это не работает в моем случае (все еще NTE_BAD_KEYSET). - person Dominique Eav; 17.11.2010

Недавно я столкнулся с подобной проблемой, прежде чем нашел этот пост. Хотя этот пост полезен для понимания проблемы, он не дал решения. Наконец, с помощью Google, я решил проблему. Хотя этот вопрос был задан 5 лет назад, я пишу свое решение здесь на случай, если оно поможет любому другому парню.

Во-первых, позвольте мне описать мою проблему: мы портировали OpenSSL 0.9.8 на наше устройство, работающее как сервер SSL, а также предоставили программу, работающую в Microsoft Windows, в качестве клиента SSL, которая также использует библиотеку OpenSSL. Эта модель клиент/сервер поддерживает аутентификацию клиентского сертификата. Клиент вызывает Microsoft Crypto-API, реализующий метод RSA_method в OpenSSL, для поддержки проверки подлинности сертификата. Пока все хорошо, прежде чем мы обновим библиотеку OpenSSL с 0.9.8 до 1.0.2 для поддержки TLSv1.2. При выборе TLSv1.2 аутентификация клиентского сертификата всегда завершалась неудачно. После отладки я обнаружил, что проблема в методе RSA_sign клиентской программы. Этот метод подписывает результат хеширования, чтобы получить цифровую подпись, которая будет использоваться в сообщении SSL Client Verify. Операция подписания рассчитывается с использованием CertFindCertificatePrivateKey/CryptCreateHash/CryptSetHashParam/CryptSignHash API, как описано в этом вопросе. Ошибка произошла при вызове CryptCreateHash, где запрошенный метод хеширования — SHA-384, но hCryptoProv, полученный с помощью CertFindCertificatePrivateKey, соответствует CSP (Microsoft Enhanced Cryptographic Provider v1.0), НЕ поддерживает хэш SHA-2. методы.

Это действительно утомительное описание. Если вы все еще продолжаете читать, я думаю, вы тоже столкнулись с подобной проблемой. Позвольте представить вам мое решение.

Моей первой реакцией было настроить параметр согласования SSL, чтобы понизить алгоритм хэширования до SHA-1, но мне это не удалось. Кажется, хэш SHA-2 обязателен для TLSv1.2. Также я пытался выполнить операцию подписания путем шифрования с помощью закрытого ключа сертификата, что также не удалось. Crypto-API не предоставляет интерфейса для шифрования закрытого ключа сертификата (я не уверен в этом, но я его не нашел). После двух дней безрезультатных попыток я нашел эту веб-страницу: http://www.componentspace.com/Forums/Topic1578.aspx. Это действительно похоже на свет в конце тоннеля. Сертификат привязывается к CSP, не поддерживающему SHA-2, если мы сможем преобразовать его в CSP, поддерживающий SHA-2, все будет в порядке. В случае неработающей ссылки я вставляю сюда метод преобразования:

openssl pkcs12 -export -in idp.pem -out new-idp.pfx -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider"

Я только что перестроил файл сертификата .pfx, как сказано на веб-странице; повторно импортировал сертификат. Тогда проблема решена.

Надеюсь, это полезно. :-)

person GrepAll    schedule 06.09.2016