.NET Framework x509Certificate2类,HasPrivateKey == true && PrivateKey == null是什么意思?

7
我正在尝试处理一个X509证书,该证书最初是使用MMC的“证书”插件导入到Windows 10计算机的CurrentUser密钥库中的。同样的过程已在Windows 8.1计算机上进行了测试并得到了相同的结果。
使用标准的PowerShell PKI模块,我正在使用Get-Item获取一个X509Certificate2对象:
$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                  : E=my.name@my.domain.us, CN=My Name

所以HasPrivateKey == True,但PrivateKey == null。我一直在尝试找出如何访问私钥以执行加密和解密。我在网上看到的示例似乎都表明X509Certificate2类的PrivateKey属性应该是readily available,但显然我错过了什么。
我已经阅读了类似的问题,比如Empty PrivateKey in x509certificate2,但没有一个能解决我的问题。我也阅读了Paul Stovell的Eight tips for working with X.509 certificates in .NET,这非常有启发性,但最终没有帮助。它确实帮助我验证了私钥存在于正确的位置,并且据我所知,具有正确的权限可以被x509Certificate2类引用:
C:\Users\My.Name\AppData\Roaming\Microsoft\SystemCertificates\My\Keys

密钥文件的名称与证书上的主题密钥标识符相匹配。

编辑:

certutil -user -store my "序列号" 的输出为:

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: E=my.name@my.domain.us, 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对象中引用?我如何获得访问权限?

1
尝试使用 $my_cert.get_PrivateKey()。它是否会抛出任何异常? - user4003407
1
@PetSerAl,我确实遇到了一个异常 - "指定的提供程序类型无效。" - Matthew Johnson
3个回答

7
您的 certutil 信息显示 Provider = Microsoft Software Key Storage Provider,这意味着私钥存储在 Windows CNG 下,而不是 Windows CryptoAPI (CAPI) 下。
.NET 4.6 增加了 GetRSAPrivateKey (扩展) 方法,以便让 PrivateKey 属性返回不是 RSACryptoServiceProvider (或 DSACryptoServiceProvider) 的内容,以避免破坏性更改。如果您可以访问该方法 (我不确定 PowerShell 使用的框架版本),那么它将解决您的问题。
但需要注意以下两点:
  1. 每次返回一个唯一的 Disposable 对象。您应该将其放在 using 语句中/在使用完毕后手动调用 Dispose (与不是唯一的 cert.PrivateKey 不同,所以不应该被处理)。
  2. 签名/验证/加密/解密方法已经移动到 RSA 基类中 (尽管具有稍微不同、更具前瞻性的签名)。

6
这可能表明以下情况之一:
1)私钥存储在密钥存储提供程序(而不是传统的加密服务提供程序)中,该提供程序受.NET支持较差,并且根本不受X509Certificate2类的PrivateKey属性支持。您可以通过运行以下命令来检查此问题:
certutil -user -store my "<CertSerialNumber>"

2) 私钥丢失。

HasPrivateKey 属性并不一定反映实际情况,对于不存在的密钥可能会显示为 True,而对于存在的密钥可能会显示为 False。请运行上述 certutil 命令以确保密钥是否存在。

如果私钥存在但绑定已损坏,您可以尝试通过运行以下命令来恢复绑定:

certutil -user -repairstore my "<CertSerialNumber>"

我已经在原帖中添加了CertUtil的输出。看起来你是对的 - 私钥是由密钥存储提供程序(KSP)存储的。我现在正在寻找使用KSP和CNG密钥的良好示例... 你知道有哪些吗? - Matthew Johnson
你需要什么? - Crypt32
我想我不需要什么了。我只是在寻找通过示例理解的捷径,但我设法将其组合在一起...感谢您在此方面的帮助! - Matthew Johnson
一般来说,为了在.NET中使用KSP,您必须运行一些未托管的C样式函数:CryptAcquireCertificatePrivateKey,并使用返回的密钥句柄实例化一个CngKey类,该类可用于执行常见的加密操作。 - Crypt32
1
这只是一个在PowerShell中使用纯本地CryptoAPI函数的示例:https://www.sysadmins.lv/blog-en/sign-data-with-ecc-cng-certificate.aspx - Crypt32
使用“Microsoft软件密钥存储提供程序”与“Microsoft增强型加密提供程序v1.0”是否会影响私钥保留?我正在调查一个问题,即我们的证书私钥在一段时间后(几天、几个月)会不知何故丢失,我发现这种情况发生的环境使用的是“Microsoft软件密钥存储提供程序”用于证书,而使用“Microsoft增强型加密提供程序v1.0”的证书似乎能够保留私钥。 - axk

2

我解决了这个问题。

由于某些原因,.NET框架无法从文件导入私钥,但是它可以从内置的Windows存储中导入,这是因为PrivateKey方法仅支持RSA和DSA密钥,根据Microsoft规范:请阅读“备注”部分下面的注释。

无论如何,要获取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) 运行代码,如果你得到一个“YES”,那么私钥不是NULL(或NOTHING),这就是你要找的。

如果你直接从文件加载相同的证书,私钥将始终为NOTHING / NULL,但HasPrivateKey会显示YES,这意味着(我知道有一个密钥,但我不知道如何理解它。当你将其导入Windows存储时,Windows会将其转换为.NET兼容格式。

希望这可以帮助你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接