Windows CryptoAPI: CryptSignHash с CALG_SHA_256 и закрытым ключом из моего хранилища ключей - PullRequest
3 голосов
/ 16 ноября 2010

Я пытаюсь сгенерировать цифровые подписи в 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: лучшее, что я могdo был успешным вызовом CryptGetUserKey, но CryptSignHash всегда завершался ошибкой с одной и той же ошибкой.

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

Ответы [ 5 ]

9 голосов
/ 24 апреля 2012

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

4 голосов
/ 21 октября 2015

Большинство предыдущих ответов содержат части реального ответа. Способ сделать это - получить HCRYPTPROV, использующий контейнер ключей и «Microsoft Enhanced RSA и 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 для получения ключа (например, CrypYPTPROV, запрос на получение которого для ключа) ).

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

1 голос
/ 16 ноября 2010

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

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

0 голосов
/ 06 сентября 2016

Недавно я столкнулся с подобной проблемой, прежде чем нашел этот пост. Хотя этот пост полезен для понимания проблемы, он не дал решения. Наконец, с помощью 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. Операция подписания рассчитывается с использованием 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, как сказано на веб-странице; повторно импортировал сертификат. Тогда проблема решена.

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

0 голосов
/ 16 ноября 2010

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

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

...