.NET Framework x509Certificate2 Class, HasPrivateKey == true && PrivateKey == null?

Я пытаюсь работать с сертификатом X509, который изначально был импортирован в хранилище ключей CurrentUser на компьютере с Windows 10 с помощью оснастки «Сертификаты» консоли MMC. Та же процедура была протестирована на компьютере с Windows 8.1 с тем же результатом.

Используя стандартный модуль PKI PowerShell, я получаю объект X509Certificate2 с помощью Get-Item:

$my_cert = Get-Item Cert:\CurrentUser\My\ADAA82188A17THUMBPRINTXXXXXXXXXXX

Результат $my_cert | fl * выглядит следующим образом:

PSPath                   : Microsoft.PowerShell.Security\Certificate::CurrentUser\My\XXXXXXXXXXXXXXXXXXX
PSParentPath             : Microsoft.PowerShell.Security\Certificate::CurrentUser\My
PSChildName              : XXXXXXXXXXXXXXXXXXX
PSDrive                  : Cert
PSProvider               : Microsoft.PowerShell.Security\Certificate
PSIsContainer            : False
EnhancedKeyUsageList     : {Secure Email (1.3.6.1.5.5.7.3.4), IP security user (1.3.6.1.5.5.7.3.7), Encrypting File
                           System (1.3.6.1.4.1.311.10.3.4), Document Signing (1.3.6.1.4.1.311.10.3.12)...}
DnsNameList              : {My Name}
SendAsTrustedIssuer      : False
EnrollmentPolicyEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
EnrollmentServerEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
PolicyId                 : {D52C406F-C279-4BF2-B7C2-EE704290DB3E}
Archived                 : False
Extensions               : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid,
                           System.Security.Cryptography.Oid, System.Security.Cryptography.Oid...}
FriendlyName             :
IssuerName               : System.Security.Cryptography.X509Certificates.X500DistinguishedName
NotAfter                 : 4/15/2017 2:15:16 PM
NotBefore                : 4/15/2016 2:15:16 PM
HasPrivateKey            : True
PrivateKey               :
PublicKey                : System.Security.Cryptography.X509Certificates.PublicKey
RawData                  : {56, 130, 19, 252...}
SerialNumber             : 4F0000002F700000000000000000000000
SubjectName              : System.Security.Cryptography.X509Certificates.X500DistinguishedName
SignatureAlgorithm       : System.Security.Cryptography.Oid
Thumbprint               : XXXXXXXXXXXXXXXXXXX
Version                  : 3
Handle                   : 2241663016272
Issuer                   : CN=Issuing CA, DC=My, DC=Domain, DC=us
Subject                  : [email protected], CN=My Name

Итак, HasPrivateKey == True, но PrivateKey == null. Я пытался выяснить, как получить доступ к закрытому ключу для шифрования и дешифрования. Все примеры, которые я видел в Интернете, похоже, указывают на то, что свойство PrivateKey класса X509Certificate2 должно быть легко доступно, но, видимо, я что-то упустил.

Я читал здесь похожие вопросы, например Пустой PrivateKey в x509certificate2, но, похоже, никто не решает моя проблема. Я также прочитал Восемь советов по работе с сертификатами X.509 в .NET Пола Стовелла, которые было очень поучительно, но в конечном итоге не помогло. Это помогло мне убедиться, что закрытый ключ существует в правильном месте и, насколько я могу судить, с правильными разрешениями, на которые должен ссылаться класс x509Certificate2:

C:\Users\My.Name\AppData\Roaming\Microsoft\SystemCertificates\My\Keys

Имя ключевого файла совпадает с идентификатором ключа субъекта в сертификате.

Изменить:

Результат certutil -user -store my "Serial Number":

Serial Number: 4f000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Issuer: CN=Issuing CA, DC=My, DC=Domain, DC=us
 NotBefore: 4/15/2016 2:15 PM
 NotAfter: 4/15/2017 2:15 PM
Subject: [email protected], CN=My Name
Non-root Certificate
Template: Userv1, User v1
Cert Hash(sha1): ad ab 82 18 8a 17 4d 75 11 04 48 7c 43 43 d4 05 b9 74 c8 4c
  Key Container = te-Userv1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  Unique container name: fcbba1aa0xxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  Provider = Microsoft Software Key Storage Provider
Encryption test passed
CertUtil: -store command completed successfully.

Какая «ключевая» информация мне здесь не хватает? Почему на закрытый ключ нет удобной ссылки из объекта X509Certificate2? Как мне получить к нему доступ?


person Matthew Johnson    schedule 19.04.2016    source источник
comment
Попробуйте $my_cert.get_PrivateKey(). Возникает ли исключение?   -  person user4003407    schedule 20.04.2016
comment
@PetSerAl, я получаю исключение - указан неверный тип провайдера.   -  person Matthew Johnson    schedule 20.04.2016


Ответы (3)


Это может указывать на одно из следующего:

1) закрытый ключ хранится у поставщика хранилища ключей (а не у устаревшего поставщика криптографических услуг), который плохо поддерживается .NET и вообще не поддерживается свойством PrivateKey класса X509Certificate2. Вы можете проверить это, выполнив следующую команду:

certutil -user -store my "<CertSerialNumber>"

2) закрытый ключ отсутствует.

Свойство HasPrivateKey необязательно отражает реальную картину и может True для несуществующих ключей или False для существующих ключей. Выполните указанную выше команду certutil, чтобы убедиться, что ключ указан.

В случае, если приватный ключ представлен, но привязки нарушены, вы можете попытаться восстановить привязки, выполнив следующую команду:

certutil -user -repairstore my "<CertSerialNumber>"
person Crypt32    schedule 20.04.2016
comment
Я добавил вывод CertUtil в свой исходный пост. Похоже, вы правы - закрытый ключ хранится у поставщика хранилища ключей (KSP). Я сейчас ищу хорошие примеры работы с ключами KSP и CNG ... Знаете какие? - person Matthew Johnson; 20.04.2016
comment
что именно тебе нужно? - person Crypt32; 20.04.2016
comment
Думаю, мне ничего не нужно. Я просто искал кратчайший путь к пониманию через примеры, но мне удалось взломать его вместе ... Спасибо за вашу помощь в этом! - person Matthew Johnson; 20.04.2016
comment
Вообще говоря, для работы с KSP в .NET необходимо запустить некоторые неуправляемые функции в стиле C: CryptAcquireCertificatePrivateKey и использовать возвращенный дескриптор ключа для создания экземпляра класса CngKey, который можно использовать для выполнения обычных криптографических операций. - person Crypt32; 20.04.2016
comment
Просто пример, который использует чистые собственные функции CryptoAPI в PowerShell: sysadmins.lv/blog-en/sign-data-with-ecc-cng-certificate.aspx - person Crypt32; 20.04.2016
comment
Может ли использование Microsoft Software Key Storage Provider по сравнению с Microsoft Enhanced Cryptographic Provider v1.0 повлиять на срок хранения закрытого ключа? Я изучаю проблему, из-за которой закрытые ключи нашего сертификата каким-то образом теряются через некоторое время (дни, месяцы), и я обнаружил одно различие в том, что в среде, где это происходит, используется поставщик хранилища ключей программного обеспечения Microsoft для сертификатов, в то время как сертификаты с Microsoft Enhanced Поставщик криптографии v1.0, похоже, сохраняет закрытые ключи. - person axk; 26.03.2021

В вашей информации certutil указано Provider = Microsoft Software Key Storage Provider, что означает, что закрытый ключ хранится в Windows CNG, а не в Windows CryptoAPI (CAPI).

.NET 4.6 добавил метод GetRSAPrivateKey (extension), чтобы упростить в противном случае критическое изменение, позволяющее свойству PrivateKey возвращать что-то, что не было RSACryptoServiceProvider (или DSACryptoServiceProvider). Если у вас есть доступ к этому методу (я не уверен, какую версию фреймворка использует PowerShell), он решит вашу проблему.

Однако следует помнить о двух вещах:

  1. GetRSAPrivateKey каждый раз возвращает уникальный объект Disposable. Вы должны поместить его в оператор using / вручную вызвать Dispose по завершении с ним (в отличие от cert.PrivateKey, который не является уникальным, поэтому его не следует удалять).
  2. Методы sign / verify // encrypt / decrypt перешли в базовый класс RSA (albiet с немного другими, более перспективными подписями).
person bartonjs    schedule 06.07.2016

Я решил проблему.

По какой-то причине .NET framework не может импортировать закрытые ключи из файлов, однако он может импортировать из встроенного хранилища Windows, потому что метод PrivateKey поддерживает только RSA, а ключи DSA находятся в соответствии с Microsoft Spec: прочтите примечания в разделе «Замечания».

В любом случае, чтобы объект PrivateKey возвращал информацию о ключе, вам необходимо сделать следующее:

1) Импортируйте файл P12 в хранилище ключей Windows, дважды щелкнув его. 2) Выберите «Импорт», когда появится запрос «Текущий пользователь». 3) Убедитесь, что вы выбрали «Сделать ключ экспортируемым»,

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

. 4) Выберите «Автоматически разместить в магазине».

5) Напишите следующий код для получения сертификата:

Dim CertDataInfo As System.Security.Cryptography.X509Certificates.X509Certificate2 
Dim Store As New System.Security.Cryptography.X509Certificates.X509Store("MY", Security.Cryptography.X509Certificates.StoreLocation.CurrentUser)

Store.Open(Security.Cryptography.X509Certificates.OpenFlags.ReadOnly)
Console.writeline (Store.Certificates.Count)

For I = 0 To Store.Certificates.Count - 1
 If Store.Certificates.Item(I).FriendlyName = "Heider Sati's Cert(48F57XTHVE)" Then
  CertDataInfo = Store.Certificates.Item(I)
 End If

 Console.writeline ("Cert: " & Store.Certificates.Item(I).FriendlyName)

Next
Store.Close()

If CertDataInfo.PrivateKey Is Nothing Then
 MsgBox("NULL")
Else
 MsgBox("YES")
End If

6) Запустите код, если вы получите ДА, значит, закрытый ключ не NULL (или НИЧЕГО), что вы и ищете.

Если вы загрузите тот же сертификат непосредственно из файла, закрытый ключ всегда будет НИЧЕГО / NULL, но HasPrivateKey скажет ДА, что означает (я знаю, что есть ключ, но все же не знаю, как его понять. Когда вы импортируете его в магазин Windows, Windows переводит его в формат, совместимый с .NET.

Надеюсь, это поможет.

person Heider Sati    schedule 05.12.2016