| ||||
| ||||
Здравствуйте, Мне нужно создать PKCS#7 структуру EnvelopedData. Вся информация для RecipientInfo берется из сертификата получателя, сессионный ключ я создаю с помощью функции CryptGenKey и экспортирую его из CSP с помощью CryptExportKey в зашифрованном виде (SIMPLEBLOB). Проблема в AlgorithmIdentifier->Parameters: насколько я понимаю, для алгоритмов шифрования RSA никаких параметров не нужно (по-крайней мере там всегда NULL), а вот что должно здесь лежать для симметричного сессионного ключа (для des-EDE3-CBC, например, или для ГОСТXXXX алгоритма реализуемого Вашим CSP)? И есть ли общий (подзодящий для любого типа алгоритма и CSP) способ получить эти параметры? Спасибо, Вадим. | ||||
Ответы: | ||||
| ||||
В WinCrypt.h для параметров вызова CryptGetKeyParam есть константа KP_INFO про которую сказано: "for putting information into an RSA envelope". Однако вызов CryptGetKeyParam падает с ошибкой NTE_BAD_TYPE :( | ||||
| ||||
Я бы посоветовал воспользоваться тесом, сделать PKCS#7 enveloped, посмотреть дампом на структуры. Описание данных в структурах в случае КриптоПро CSP здесь http://www.ietf.org/internet-drafts/draft-ietf-smime-gost-02.txt. Если шифруете des’ом то KeyEncryptionAlgorithm RSAEncryption. Самое простое описание PKCS#7 в RFC2315. А пример его создания можно посмотреть например в OpenSSL. | ||||
| ||||
Хорошо, это я понимаю. Действительно, для каждого _конкретного_ алгоритма можно найти его описание и собрать AlgorithmIdentifier в соответсвии с ним. Но меня интересует несколько другой вопрос: возможно ли написать код неким универсальным образом, подходящим для _любого_ алгоритма. В идеале (это первое, что приходит в голову) это ведь забота самого CSP вернуть для указанного ключа его параметры, закодированные в соответствии со стандартом. Имеея на входе HCRYPTKEY CSP знает про ключ все и с такой задачей вполне может справиться! Кроме того, функция CryptImportKey на вход принимает (в случае зашифрованного открытым ключом получателя сессионного ключа) структуру SIMPLEBLOB, которая в своем заголовке никаких дополнительных параметров (кроме 2-x ALG_ID) не содержит, но ключ замечательно импортится и работает. Тот вариант кода который есть у меня сейчас, игнорирует параметры алгоритма, однако замечательно работает с алгоритмами DES, 3DES, RC2, RС4 - а ведь по стандарту для них дополнительные параметры должны передаваться. Проясните, пожалуйста, этот момент. | ||||
| ||||
Да, забыл отметить, что сейчас я использую M$ RSA_FULL провайдер. Поддержка Крипто-Про CSP пока только планируется, но _очень_ желательна. | ||||
| ||||
И все-таки, не могли бы Вы ответить на мой вопрос? Я только что проверил - XEnroll, к примеру, замечательно формирует AlgorithmIdentifier для CryptoPro CSP, хотя понятное дело ни о каких ГОСТ’овых алгоритмах он ничего не знает. Вопрос: как он это делает? | ||||
| ||||
Собственно последний мой вопрос довольно-таки глуп - понятно как: XEnroll создает запрос на сертификат, следовательно, все, что ему нужно, это закодировать открытый ключ будущего сертификата, а это умеет делать функция CryptExportPublicKeyInfo. Я немного покопал ее и нашел подтверждение своих догадок (см.предыдущие посты): все что делает эта функция, это вызывает функцию соответствующего CSP используя информацию об OID из реестра (CryptGetOIDFunctionAddress для CryptDllEncodePublicKeyAndParameters). Таким образом, всю работу по формированию параметров алгоритма выполняет все же сам CSP. Вопрос: могу ли я воспользоваться этим путем для формирования параметров сессионного ключа? И если могу, то как? | ||||
| ||||
Насчет универсальности. Селать можно, но не универсально, а с ограничениями. Предположим, вам нужно зашифровать письмо в адрес нескольких получателей. У вас есть их сертификаты. Что делаем. Смотрим все сертификаты, если открытый ключ RSA или DH, то для P7 можно использовать любой из алгоритмов DES, 3DES, Rc2, RC4 T.e. делаете сессионный ключ шифруете им все сообщение и его для каждого получателя шифруете если сертификат получателя RSA - RSAencryption, если DH то нужно делать ключ согласования и им шифровать сессионный. Если же в списке получателей сертификат с ГОСТом, то не получится шифровать content одновременно на разных алгоритмах, либо ГОСТ либо какой то импортный. | ||||
| ||||
Наверное, мне нужно немного больше рассказать о том, для чего мне все это надо :-) В разрабатываемой мной системе хранятся файлы, их надо шифровать и подписывать. При этом сам файл (возможно зашифрованный) хранится в одном месте (таблице), а вся остальная информация (его подписи, ключи к нему) – в другом. Зачем это надо? Что бы лишний раз не гонять файл по сети, если нужно, например, просмотреть список подписавших его лиц (потом, когда захочется подпись проверить, файл естественно придется закачать, но это потом). С шифрованием то же самое: для того, чтобы разрешить доступ к файлу кому-либо еще нет необходимости закачивать сам файл – только BLOB с ключом (сессионный ключ, зашифрованный открытым ключом пользователя). С подписью все просто – я использую CryptMsgOpenToEncode с CMSG_DETACHED_FLAG для каждого подписанта и сохраняю полученную подпись в специальную таблицу. Примерно это же мне нужно сделать и в случае шифрования, только вот CryptMsgOpenToEncode мне здесь уже не воспользоваться. Поэтому я и делаю все руками: создаю сессионный ключ, экспортирую его и т.д. Алгоритм такой: пользователь создает сессионный ключ вызовом CreateKey моего компонента, затем использует его для шифрования (EncryptStream) и сохраняет в базе. В последствии он использует его для расшифрования (DecryptStream). Кроме того, он может “поделиться” ключом к файлу с другим пользователем (DuplicateKey). В общем, это очень похоже на то, как шифруются файлы в EFS под Win2003. CreateKey := CryptGenKey, CryptExportKey, <упаковка в ASN1> EncryptStream := <распаковка ASN1>, CryptImportKey, CryptEncrypt DecryptStream := <распаковка ASN1>, CryptImportKey, CryptDecrypt DuplicateKey := <распаковка ASN1>, CryptImportKey, CreateKey Как будет зашифрован сессионный ключ, меня интересует мало (это забота CryptExportKey выбрать RSAEncryption или что-то другое), однако я не могу каким-либо образом ограничивать выбор CSP и алгоритмов самого сессионного ключа – система должна быть готова ко всему (типа платформа и все такое). Проблема только в параметрах ключа: мне нужно уметь универсальным образом получить их (для последующего импорта в CSP) и закодировать в AlgorithmIdentifier (т.к. запланирована функциональность экспорта файлов во внешние системы в PKCS7 SignedData и EnvelopedData форматах, и мне придется их руками собирать из имеющихся кусочков). | ||||
| ||||
В общем виде не получится. Ведь когда вы вызываете CryptGenKey, вам нужно указать провайдер и алгоритм. А ALG_ID, как описано зависит от провайдера: ALG_ID structure identifying the algorithm for which the key is to be generated. Values for this parameter vary depending on the CSP used. Соответственно эту информацию нужно будет где то сохранить. "это забота CryptExportKey выбрать RSAEncryption или что-то другое" И не может CryptExportKey выбрать то или другое, т.к. на вход этой функции должен быть подготовлен ключ експорта. | ||||
| ||||
Все правильно, под фразой “это забота CryptExportKey выбрать RSAEncryption или что-то другое” я имел в виду, что ключ экспорта я получаю с помощью функции CryptImportPublicKeyInfo по сертификату получателя, так что здесь ничего хардкодить не приходится. По поводу ALG_ID – с этим я смирился, буду сохранять тип провайдера и алгоритма внутри своей ASN1 структуры. А передавать мне их будет конечное приложение (уж оно-то, будем надеяться, знает с каким CSP работает). На самом деле все мои терзания (захардкодить 5-6 известных мне алгоритмов не сложно) произрастают из одного: информация-то об открытом ключе ведь может формироваться самим CSP! Неужели нельзя его напрячь и на сессионный ключ? (Естественно посредством CryptoAPI, если Вы это реализуете в непосредственно в DLL своего CSP, меня это не спасет). Как говорится, где же справедливость!? | ||||
| ||||
Есть функция CryptGetProvParam с параметрами PP_ENUMALGS, PP_ENUMALGS_EX, которая должна возвращать список ALG_ID-ов, поддерживаемых конкретным CSP. | ||||
| ||||
Таак... теперь еще оказывается, что нельзя унифицировать процедуру экспорта сессионного ключа. Стандартная последовательность: hKey = CryptGenKey(key_params); hPubKey = CryptImportPublicKeyInfo(recipient_certificate); pbKeyData = CryptExportKey(hKey, hPubKey, SIMPLEBLOB); …не подходит для Вашего CSP! (словил ту самую пресловутую ошибку NTE_BAD_KEY_STATE). Да и формат блоба для разных CSP может быть разный. Что-то перестает мне нравиться CryptAPI :( | ||||
| ||||
Раз не нравится - купите готовое приложение, которое умеет шифровать и подписывать с использованием сертификатов :) Например, http://www.cryptopro.ru/CryptoPro/products/command.asp | ||||