Насчет того, как это выглядит на .NET не уверен, так как использовал низкоуровневое CryptoAPI. Как я понимаю, есть 2 варианта - если уже вычислен хэш, то можно подписать хэш (подходит если вычислять хэш массивного файла на одном компьютере, сервере, например, и передавать на второй компьютер, клиента с контейнером закрытого ключа); если не вычислен хэш, можно подписывать сами данные другим методом класса.
Про ключи - в алгоритмах гост генерируется ключевая пара, для подписания используется закрытый ключ, соответствующий ему открытый ключ включается в сертификат и используется для проверки. Из самого сертификата закрытый ключ никак не выделить, там только открытый ключ.
Однако предположим, что сертификат установлен в хнанилище сертификатов "Личные" со ссылкой на контейнер закрытого ключа, содержащий ключевую пару (и открытый и закрытый ключи). Тогда по отдельному файлу сертификата можно создать контекст сертификата, открыть хранилище "Личные", найти в хранилище такой же сертификат (по хэшу сертификата или по открытому ключу или по CN или по издателю и номеру или по сертификату целиком - способ поиска указывается флагами) и получить контекст криптопровайдера связанный с сертификатом в хранилище и с контейнером закрытого ключа. Есть правда плавающая проблема, что поиск "из коробки" бывает ничего не находит по некоторым сертификатам хотя они наверняка есть в хранилище и без проблем перечисляются - в таком случае проще перечислить все сертификаты хранилища и каждый "вручную" (в смысле в своем коде) сравнить с исходным.
На этом форуме вроде были примеры как выделить сертификат, подписать и проверить подпись на .NET, просто надо найти рабочий пример в соседних темах. Изложу как это работает в низкоуровневых функциях для общей картины, на .NET попроще, разбор подписи и проверка сертификатов удобнее, но в целом механика такая же.
Для проверки низкоуровневыми функциями файл сертификата читается в память, из сертификата в памяти создается контекст сертификата (который сам по себе содержит как закодированные данные как в сертификате, так и частично раскодированные данные), из раскодированной части контекста выделяется часть с оберткой открытого ключа. Создается с флагом VERIFY_CONTEXT контекст криптопровайдера (c таким флагом не нужна привязка к контейнеру закрытого ключа, но и функции связанные с закрытым ключом выдадут ошибку), в него импортируется обвязка открытого ключа и получается дескриптор открытого ключа. На этом же контексте (для проверки как я понимаю не важно чтобы контекст ключа и хэша совпадал, но зачем их плодить) криптопровайдера создается объект хэша, хэшируются подписанные данные (заталкивать значение хэша в объект хэша не пробовал). Из электронной подписи "в сборе" выделяется значение подписи (plain, "голая" подпись - в 2 раза длинее чем хэш). Затем дескриптор хэша, значение подписи и дескриптор открытого ключа подаются на функцию проверки. Если вернулось false, то надо смотреть код ошибки - неверная подпись или что-то напутано с параметрами и проверить не удалось. Если true то подпись математически корректна, надо проверять цепочку сертификатов.
Для подписи низкоуровневыми фунуциями создается контекст криптопровайдера с указанием имени контейнера и без флага VERIFY_CONTEXT (в ранних версиях КриптоПро 4.0 нужно посмотреть имя контейнера по ссылке в хранилище потом создавать контекст) либо функцией CryptAcquireCertificatePrivateKey (функция допустима без тематических исследований с 4.0 R3 - контекст криптопровайдера получается по ссылке из хранилища сертификатов, на вход нужен контекст сертификата из хранилища с прикрепленной ссылкой на контейнер, а не просто контекст сертификата созданный из файла сертификата). Типовая ситуация - перечисляются все сертификаты в хранилище Личные, контексты сертификатов дублируются в какой-то массив (так как первая копия уничтожается при перечислении), выводятся пользователю на выбор, потом из выбранного контекста сертификата получается контекст криптопровайдера, дубли контекстов сертификатов освобождаются.
На этом контексте криптопровайдера (для подписания важно чтобы контекст криптопровайдера для создания хэша был со ссылкой на контейнер!) создается хэш, хэшируются подписываемые данные (заталкивать значение хэша в объект хэша не пробовал). Затем на функцию подписания подаются дескриптор хэша (неявно содержащий ссылку на контекст криптопровайдера, содержащий ссылку на контейнер), тип ключа (в контейнере могут быть AT_SIGNATURE или AT_KEYEXCHANGE ключи, можно узнать какой 1) из ссылки на контейнер, в ссылке из хранилища сертификатов указано какому именно ключу соответствует сертификат. 2) В гост-2001 без проблем определялось по параметрам открытого ключа, в гост-2012 пока не знаю надежного метода определить по сертификату, но если в сертификате гост-2012 указаны параметры от гост-2001 можно по ним), буфер для значения подписи. Вообще для низкоуровневых функций как правило деляется 2 вызова - первый чтобы получить размер буфера и второй чтобы получить сами данные, но если так сделать при подписании у пользователя пин-код запросит дважды - 90% пользователей подумают что ошиблись и будет паника. Значение подписи гост фиксированного размера и можно пропустить первый запрос, выделив сразу буфер в два раза больше чем для значения хэша. Затем значение подписи с дополнительными данными упаковывается в электронную подпись "в сборе". Детали зависят от формата подписи.
Да, если захотите вдруг вытащить "голый" закрытый ключ из контейнера, то у КриптоПро на этот счет позиция не выгружать ключ в голом виде, нет ни одного официального способа это сделать. Есть неофициальные, но большинство не подходит для гост-2012.
Отредактировано пользователем 4 июня 2019 г. 12:27:21(UTC)
| Причина: Не указана