29

As the title suggests I would like to export my private key without using OpenSSL or any other third party tool. If I need a .cer file or .pfx file I can easily export these via MMC or PowerShell pkiclient but I can't find a way to get the private key.

https://learn.microsoft.com/en-us/powershell/module/pkiclient/export-certificate?view=win10-ps

Using an online tool like https://www.sslshopper.com/ssl-converter.html is not OK.

PSVersion:

PS C:\Users\oscar> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.17134.228
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17134.228
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

I can get the public key like this:

(Get-PfxCertificate -FilePath C:\Users\oscar\Desktop\localhost.pfx).GetPublicKey()

And export the entire certificate like this:

(Get-PfxCertificate -FilePath C:\Users\oscar\Desktop\localhost.pfx).GetRawCertData()

Result from

PS C:\Users\oscar> $mypwd = ConvertTo-SecureString -String "MyPassword" -Force -AsPlainText
PS C:\Users\oscar> $mypfx = Get-PfxData -FilePath C:\Users\oscar\Desktop\localhost.pfx -Password $mypwd
PS C:\Users\oscar> $mypfx

OtherCertificates EndEntityCertificates
----------------- ---------------------
{}                {[Subject]...


PS C:\Users\oscar> $mypfx.EndEntityCertificates

Thumbprint                                Subject
----------                                -------
8ED4971564E35099D6DB490C3756E2AD43AAAAAA  CN=localhost

Tested the command from @Brad but I got the error below.

Private key is NOT plain text exportable

certutil -exportPFX -p "myPassword" -privatekey -user my <Certificate Serial Number> C:\localhost.pfx

enter image description here

Similar to Certificate Export Wizard in MMC certificates, only export to .pfx available if the key is included.

enter image description here

Ogglas
  • 62,132
  • 37
  • 328
  • 418
  • You certainly need a `.pfx` file as `.cer` files don't store private keys. What's your `$PSVersionTable` ? Can you use `Get-PfxData -FilePath 'mycertificate.pfx' -Password (ConvertTo-SecureString -Force -AsPlainText -String 'MyClearTextPassword')` ? – Petru Zaharia Oct 23 '18 at 05:38
  • @PetruZaharia Yes I'm aware, wrote that as an example of what you can export. :) Updated the question with PSVersion and what I have tried. I can but I have not found a way to export the private key. – Ogglas Oct 23 '18 at 06:19
  • Regarding `certutil`, I had the same problem. I could export `.pfx` file with private key using Powershell: `Export-PfxCertificate -Cert cert:\CurrentUser\Root\xyz -Force -FilePath keystore.pfx -Password (ConvertTo-SecureString password -AsPlainText -Force)` The hard part: You need to find the cert thumbprint using something like: `ls cert:\CurrentUser\Root` – kevinarpe Jan 05 '23 at 13:54

6 Answers6

14

I had the same problem and solved it with the help of PSPKI Powershell module from PS Gallery. While I understand that you look for a solution that preferably uses some built in functionality in Windows, installing a module from PS Gallery might be acceptable. At least it was in my case.

First install the PSPKI module (I assume hat the PSGallery repository has already been set up):

Install-Module -Name PSPKI

The PSPKI module provides a Cmdlet Convert-PfxToPem which converts a pfx-file to a pem-file which contains the certificate and pirvate key as base64-encoded text:

Convert-PfxToPem -InputFile C:\path\to\pfx\file.pfx -Outputfile C:\path\to\pem\file.pem

Now, all we need to do is splitting the pem-file with some regex magic. For example, like this:

(Get-Content C:\path\to\pem\file.pem -Raw) -match "(?ms)(\s*((?<privatekey>-----BEGIN PRIVATE KEY-----.*?-
----END PRIVATE KEY-----)|(?<certificate>-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----))\s*){2}"

$Matches["privatekey"] | Set-Content "C:\path\to\key\file.pem"
$Matches["certificate"] | Set-Content "C:\path\to\certificate\file.pem"
Christoph Böhme
  • 3,766
  • 1
  • 19
  • 29
  • 5
    Good answer but I would prefer to not use any third party library as you say. However since this is the best answer so far I will mark it as accepted until there is a better alternative. :) – Ogglas Nov 03 '20 at 13:12
  • I added a PowerShell script that incorporates the .NET approach to exporting the private key to a Pkcs8 PEM file. I want to also point out that the PSPKI Convert-PfxToPem is very low level; using PInvoke to call Win32 methods. Since .NET added support for CNG (Crypto Next Gen), we have all the capability we need via the System.Security.Cryptography namespace. – RashadRivera Jul 21 '21 at 13:21
  • You can do this **without** the **third party** library: `$cert = Get-PfxCertificate -FilePath $pfxFilePath; Export-Certificate -FilePath $derFilePath -Cert $cert; certutil -encode $derFilePath $pemFilePath | Out-Null` Now that you have pem file follow the rest of the posted answer. (I wish we could format code better in comments...) – S. Melted Dec 06 '21 at 16:59
  • @S.Melted This won't include the private key. – stackprotector Jul 15 '22 at 14:04
4

I found Panos.G's answer quite promising, but did not get it to work. All three described methods are not available on my certificate object. After more digging, I came up with the following solution:

Note: It works, if you read the certificate from the certificate store. It does not work, if you read in a .pfx file with Get-PfxCertificate, for example. If you just have it as a file, you can install it in your certificate store to be able to read it from there as follows.

# Read the certificate from the certificate store
# In this example, I use the certificate thumbprint to identify the certificate.
$cert = Get-ChildItem Cert:\ -Recurse | ? {$_.Thumbprint -eq '<THUMBPRINT_OF_CERTIFICATE>'}

# Read the private key into an RSA CNG object:
$RSACng = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)

# Get the bytes of the private key
$KeyBytes = $RSACng.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)

# Encode the bytes (Base64)
$KeyBase64 = [System.Convert]::ToBase64String($KeyBytes, [System.Base64FormattingOptions]::InsertLineBreaks)

# Put it all together
$KeyPem = @"
-----BEGIN PRIVATE KEY-----
$KeyBase64
-----END PRIVATE KEY-----
"@

Docs:

stackprotector
  • 10,498
  • 4
  • 35
  • 64
3

Based on the existing answer by stackprotector I'd like to add a fragment that works when reading directly from a .pfx file.

When reading from a file, you must use a method from .net core that allows specifying the X509StorageFlag Exportable instead of using PowerShells Get-PfxCertificate

# Password is a plain string, not a securestring
$cert=New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( 
      $filename, 
      $password, 
      [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

# now continue as with the other solutions
# Read the private key into an RSA CNG object:
$RSACng = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)

# Get the bytes of the private key
$KeyBytes = $RSACng.Key.Export([Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)

# Encode the bytes (Base64)
$KeyBase64 = [Convert]::ToBase64String($KeyBytes, [Base64FormattingOptions]::InsertLineBreaks)

# Put it all together
$KeyPem = @"
-----BEGIN PRIVATE KEY-----
$KeyBase64
-----END PRIVATE KEY-----
"@
mabene
  • 101
  • 4
1

Based on what PowerShellGuy mentioned.

Will that work for you?

# first get your cert, either via pure .NET, or through the PSDrive (Cert:\)

# this is just an example

# get cert from PSDrive
$cert = Get-ChildItem Cert:\LocalMachine\My | where Subject -eq 'CN=MySubject'

# get cert from .NET
$My     = [System.Security.Cryptography.X509Certificates.StoreName]::My
$Store  = [System.Security.Cryptography.X509Certificates.X509Store]::new($My,'localmachine')
$Store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::MaxAllowed)

$cert   = $Store.Certificates | where Subject -eq 'CN=MySubject'

# get private key
# PKCS8, way #1
$BytesPkcs8 = $cert.PrivateKey.ExportPkcs8PrivateKey()
[System.Convert]::ToBase64String($BytesPkcs8)

# PKCS8, way #2
$Pkcs = [System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob
$BytesPkcs8 = $cert.PrivateKey.Key.Export($Pkcs)
[System.Convert]::ToBase64String($BytesPkcs8)

# RSA
$BytesRsa = $cert.PrivateKey.ExportRSAPrivateKey()
[System.Convert]::ToBase64String($BytesRsa)

So is that Base64 string what you're looking for?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Panos.G
  • 9
  • 1
0

Hm. Have you tried opening the cert store, and getting the private key that way? Pretty sure this will only work with RSA/DSA certs though.

$store = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My,"localmachine")
$store.Open("MaxAllowed")
$cert = $store.Certificates | ?{$_.subject -match "^CN=asdasd"}
$cert.PrivateKey.ToXmlString($false)
$store.Close()

PowerShellGuy
  • 733
  • 2
  • 8
  • I did get a value from this but it has to be modified. Your code results in:`base64 valueAQAB`. However with `$cert.PrivateKey.ToXmlString(1)` and then converting that with RSA Key Converter - XML to PEM I do get the private key in base64. I have not found a way to do this with a built in Windows util though. – Ogglas Nov 03 '20 at 13:39
-1

If I understand correctly certutil should do it for you.

certutil -exportPFX -p "ThePasswordToKeyonPFXFile" my [serialNumberOfCert] [fileNameOfPFx]

Brad
  • 1
  • 1
  • Looked good but even though the helper said `Export certificate and private key` I got the message `Private key is NOT plain text exportable`. I could only export to `.pfx`. See updated question for print screen. – Ogglas Aug 27 '20 at 15:39