问题
I'm attempting to work with an X509 certificate that was originally imported into the CurrentUser keystore on a Windows 10 computer using the "Certificates" snap-in of an MMC. The same procedure has been tested on a Windows 8.1 computer with the same result.
Using the standard PowerShell PKI module, I'm getting an X509Certificate2 object using Get-Item:
$my_cert = Get-Item Cert:\CurrentUser\My\ADAA82188A17THUMBPRINTXXXXXXXXXXX
The output of $my_cert | fl *
is as follows:
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
So HasPrivateKey == True, but PrivateKey == null. I've been trying to figure out how to gain access to the private key to perform encryption and decryption. The examples I've seen online all seem to indicate the PrivateKey property of the X509Certificate2 class should be readily available, but apparently I've missed something.
I've read similar questions here, such as Empty PrivateKey in x509certificate2, but none seem to resolve my issue. I've also read Eight tips for working with X.509 certificates in .NET by Paul Stovell, which was very enlightening, but ultimately didn't help. It did help me verify that the Private Key exists in the correct place and, as far as I can tell, with the correct permissions to be referenced by the x509Certificate2 class:
C:\Users\My.Name\AppData\Roaming\Microsoft\SystemCertificates\My\Keys
The name of the key file matches to the Subject Key Identifier on the certificate.
Edit:
The output of certutil -user -store my "Serial Number"
is:
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.
What "key" piece of information am I missing here? Why isn't the private key conveniently referenced from the X509Certificate2 object? How do I gain access to it?
回答1:
This may indicate one of the following:
1) the private key is stored in the Key Storage Provider (rather than legacy crypto service provider) which is poorly supported by .NET and not supported by PrivateKey
property of X509Certificate2
class at all. You can check this by running the following command:
certutil -user -store my "<CertSerialNumber>"
2) the private key is missing.
HasPrivateKey
property doesn't necessary reflect the actual picture and may True
for non-existent keys or False
for existing keys. Run the certutil command above to make sure if the key is presented.
In the case if private key is presented, but the bindings are broken, you can try to restore bindings by running the following command:
certutil -user -repairstore my "<CertSerialNumber>"
回答2:
Your certutil information shows Provider = Microsoft Software Key Storage Provider
which means that the private key is stored under Windows CNG, instead of Windows CryptoAPI (CAPI).
.NET 4.6 added the GetRSAPrivateKey (extension) method to facilitate the otherwise breaking change of letting the PrivateKey property return something which was not an RSACryptoServiceProvider (or DSACryptoServiceProvider). If you have access to that method (I'm not sure what version of the framework PowerShell uses) then it would solve your problem.
Two things to be aware of, though:
- GetRSAPrivateKey returns a unique Disposable object each time. You should put it in a using statement/manually call Dispose when finished with it (as opposed to cert.PrivateKey, which isn't unique, so shouldn't be Disposed).
- The sign/verify//encrypt/decrypt methods have moved down to the RSA base class (albiet with slightly different, more forward-looking, signatures).
回答3:
I resolved the problem.
For some reason, .NET framework can't import private keys from files, however, it can import from the built-in windows store, this is because the PrivateKey method only supports RSA and DSA keys are per the Microsoft Spec: read the notes under "Remarks" section.
Anyhow, to get a PrivateKey object to return the key info, You need to do the following:
1) Import your P12 file into the Windows Keystore by double-clicking it. 2) Select "Import" when prompted into "Current User" 3) Make sure you select "Make Key Exportable",
if this option is not available then your certificate has no private key
. 4) Select "Automatically place into store"
5) Write the following code to retrieve your certificate:
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) Run the code, if you get a YES then the private key is not NULL (or NOTHING), which is what you are looking for.
If you load the same certificate directly from the file, the Private key will always be NOTHING / NULL , but the HasPrivateKey will say YES, which means (I know there is a key, but yet, I don't know how to understand it. When you import it into the Windows store, then Windows does translate it into a .NET-Compatible format.
I hope this helps.
来源:https://stackoverflow.com/questions/36730596/net-framework-x509certificate2-class-hasprivatekey-true-privatekey-nu