Skip to content

Instantly share code, notes, and snippets.

@Bill-Stewart
Created May 18, 2026 15:42
Show Gist options
  • Select an option

  • Save Bill-Stewart/b33f7c3b25b140d1871c9bbe5ddec145 to your computer and use it in GitHub Desktop.

Select an option

Save Bill-Stewart/b33f7c3b25b140d1871c9bbe5ddec145 to your computer and use it in GitHub Desktop.
# Out-CertificatePrivateKey.ps1
# Written by Bill Stewart (bstewart AT iname.com)
#requires -version 5.1
# Version history:
#
# 2026-05-18
# * Initial version.
<#
.SYNOPSIS
Outputs a certificate's private key in RSA PKCS#8 base64-encoded (PEM) format.
.DESCRIPTION
Outputs a certificate's private key in RSA PKCS#8 base64-encoded (PEM) format. You can specify either the path to a PFX file or the SHA1 thumbprint (hash) of a certificate in the certificate store.
.PARAMETER Path
Specifies the path of a PFX (PKCS#12) file that contains a certificate and its private key.
.PARAMETER Password
Specifies the password for the PFX file. If you omit this parameter, you will be prompted to enter the password.
.PARAMETER Thumbprint
Specifies the SHA1 thumbprint (hash) of a certificate in the certificate store. This parameter is a 40-character hexadecimal string. The certificate must have a private key and the private key must be marked as exportable.
.INPUTS
None
.OUTPUTS
String
.NOTES
The certificate's private key will be output in RSA PKCS#8 format and base64-encoded (PEM format), as in the following example:
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWsEjZQRdgGJi26Lr6y5KOmoxp
L+WV8KoPK4qh0sBVEvgclhk/chxcVYjdNuUyaFc/4H5jsq22GfR7XxfRcGY6mcjicJl9royHRrWB
8RXDX7/dDgcDIqrkmxCAmunoYAyR54DZmJf/eENsAZ0xog5f+sxpWSO0m6/N64ML4dhUpbbGrEmn
ox6EcIw9ktZrLOmZsPSv1X9/1Az2W4PKJLnMcKhSzI6awJ9jpftOLP8E87/QytrZm3VL6bw1/tRZ
uSE2Ndu3+jBJu4caZ0oKSYzyd9zGRYapJQ1ujrmsgCewE13V5viu9vQS8aUFFOjfiSZ2xGDCU2m6
hQNUGVG82AKBAgMBAAECggEBAISeeSHbl7kA+LVkprxuLKZyyfHgaI5rEGI2n3i/W5gyE5p1Djun
HeXb4KPmWUb+NYd8AhdMNQqcle3wHCKAsGXawcE/lVGSFbUq9MzERJFgAIFpPOOu8qotL9CB4aRu
UNWAkJPaM2rYu61ogdY7KW4Y21Tof6n90mf96AHbDWuboMCCwhGeIwvsvkvQ3ZojwpJvwEHOcjDo
uRhazbPvjJ1sKbJh/Ku1gLD/bM0Pe/66pUXULkHaxNeby4dnWoe8PXWc9YVBi631TrtoE1xer2nr
UYcrIZ7PIp9YwrsboRmLqX37sHPWYX/a8B3K2Dj3+hmwvlvF0CgFwwfU2qFvEJECgYEA8QUSb6M+
Sv1fkEMqJ4agxEUDSq20YQUcbxqAJabcv/p3NttIOpcM123f5qtWPsZ2Fd+2IOhSEsDjxZLwCI2N
1TZjZLLQoamHFnvpY0Ke5YE0Hyevm7LKJ5d8Ket4luFdF7h+DA0K5NYywPbv0u9y0S1S2892J6id
bbMB54opaC8CgYEA5AhABDr6FOY/sCsuUjJeSI0lIrn5VOp/gILSp1I4AwveB7jaaNIWTGibhlrG
TZ+NZYs9BOAn+tkggJHj6R51RiuDx+vmS2xFJ6Q9tpeYXi/2sueHeFFiGPgCVcF+xlJwI85O6CIY
CYgx2hwwh/bQbTRUjrV5jMBYRmYH5OJp5E8CgYBIsgXwhCVKywdi5M83IsUkzcgDY0cvqDH+VNjo
Al88zFfexN8RRPQLmklZMr6NqqB9Un+Fh20R2PoaYophmgh4kbIC8mg5CoADSRaI6NQyVvkYmIjA
Bnow3OEHZ810fL7uY6gpDFoH0Y93YkPNtIlGDEycngN+TL5HEyH061g34wKBgQDeZUFyHsb6jRj+
Sw7xMZiMr8PRG4oVjGLvxieG8oQYTIGuDFZPNt5yhO9x2KRg3kR8DZFkaO31E8BERIUAPiloV1Y+
LE89rgnAQMnDhBQhgCvdjjusV8sRyQgV9RIcgBhIg5Snh2DkIwVj0GxwnoHmj8wMJue2I320oxv9
2egmtwKBgA7eDuxYat7QueREeU7k3zvdZlo9WdTloJiAbXumPtxfrJPrkogKENmtXtzZ+WHPhc1H
wo37f20/pLrbnVEfu/rXFG/V5LnhYoYA9zkyl4JL5i/CZAQHfy6ODJYwAWUb9QZn9qABviqCj6Lx
rUjWoHcK31lw0j6ysbeaSTaNmNKF
-----END PRIVATE KEY-----
The output is a single string with embedded line breaks (rather than an array).
.EXAMPLE
PS > Out-CertificatePrivateKey -Thumbprint dd808ad30526413d816a14b41ed27867f48a8373
Outputs the private key for the specified certificate in the certificate store. The private key must exist and must be marked as exportable.
.EXAMPLE
PS > Out-CertificatePrivateKey myCert.pfx
Outputs the private key for the certificate stored in the myCert.pfx file. You will be prompted to enter the PFX file password.
#>
[CmdletBinding(DefaultParameterSetName = "PFXFile")]
param(
[Parameter(ParameterSetName = "PFXFile",Position = 0,Mandatory)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(ParameterSetName = "PFXFile")]
[Security.SecureString]
$Password,
[Parameter(ParameterSetName = "Thumbprint",Mandatory)]
[ValidateNotNullOrEmpty()]
[String]
$Thumbprint
)
if ( [Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT ) {
throw "Windows platform required"
}
# Windows error codes
$ERROR_FILE_NOT_FOUND = 0x00000002 # 'The system cannot find the file specified'
$ERROR_INVALID_PASSWORD = 0x00000056 # 'The specified network password is not correct'
$CRYPT_E_BAD_ENCODE = 0x80092002 # 'An error occurred during encode or decode operation'
$CRYPT_E_UNEXPECTED_MSG_TYPE = 0x8009200A # 'The certificate does not have a property that references a private key'
$CRYPT_E_NO_DECRYPT_CERT = 0x8009200C # 'Cannot find the certificate and private key to use for decryption'
# Writes a custom error to the error stream
function Write-CustomError {
param(
[Int]
$errorCode,
[String]
$subject,
[Management.Automation.ErrorCategory]
$errorCategory = [Management.Automation.ErrorCategory]::NotSpecified
)
# Get localized error message for the error code
$message = ([ComponentModel.Win32Exception] $errorCode).Message
# Append subject to message if specified
if ( $subject ) { $message += " - '$subject'" }
# Append error code to message (hex string if < 0, decimal if >= 0)
$message += ((" (0x{0:X8})" -f $errorCode),(" ({0})" -f $errorCode))[$errorCode -ge 0]
# Build ErrorRecord object
$errorRecord = New-Object Management.Automation.ErrorRecord((New-Object ComponentModel.Win32Exception($errorCode,
$message)),$message,$errorCategory,$null)
# Use parent scope PSCmdlet to write error (so source reflects script name)
(Get-Variable "PSCmdlet" -Scope 1).Value.WriteError($errorRecord)
}
# Outputs the low 16 bits of a 32-bit error code value
function Get-Win32ErrorCode {
param(
[Int]
$value
)
[BitConverter]::ToUInt32([BitConverter]::GetBytes($value),0) -band 0xFFFF
}
switch ( $PSCmdlet.ParameterSetName ) {
"Thumbprint" {
# The same certificate might be in multiple locations in the store
$X509Certs = Get-ChildItem Cert:\ -Recurse -Include $Thumbprint
if ( $null -eq $X509Certs ) {
# If the thumbprint was not found, write an error containing error
# code CRYPT_E_NO_DECRYPT_CERT
$ErrorCode = $CRYPT_E_NO_DECRYPT_CERT
Write-CustomError $ErrorCode $Thumbprint ([Management.Automation.ErrorCategory]::ObjectNotFound)
exit $ErrorCode
}
# For the cert(s) we found, select the first one that has a private key
$X509Cert2 = $X509Certs | Where-Object { $_.HasPrivateKey } | Select-Object -First 1
if ( $null -eq $X509Cert2 ) {
# If we found the certificate in the store but not the private key, write
# an error containing error code CRYPT_E_UNEXPECTED_MSG_TYPE
$ErrorCode = $CRYPT_E_UNEXPECTED_MSG_TYPE
Write-CustomError $ErrorCode -errorCategory ([Management.Automation.ErrorCategory]::ObjectNotFound)
exit $ErrorCode
}
}
"PFXFile" {
# Prompt for PFX file password if -Password parameter was not specified
if ( -not $PSBoundParameters.ContainsKey("Password") ) {
$Password = Read-Host "Password" -AsSecureString
}
try {
$X509Cert2FilePath = (Resolve-Path $Path -ErrorAction Stop).ProviderPath
}
catch {
# Write an error containing the error code ERROR_FILE_NOT_FOUND if we got
# "item not found"; otherwise, write an error containing the HResult code
# in the exception
$ErrorCode = ($_.Exception.HResult,$ERROR_FILE_NOT_FOUND)[$_.Exception.GetType() -eq [Management.Automation.ItemNotFoundException]]
Write-CustomError $ErrorCode $Path $_.CategoryInfo.Category
exit $ErrorCode
}
try {
$X509Cert2 = New-Object Security.Cryptography.X509Certificates.X509Certificate2($X509Cert2FilePath,
$Password,[Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
}
catch {
# If the error object's inner exception HResult code's least significant
# 16 bits is ERROR_INVALID_PASSWORD, write an error containing that error
# code; otherwise, write an error containing CRYPT_E_BAD_ENCODE
$ErrorCode = ($CRYPT_E_BAD_ENCODE,$ERROR_INVALID_PASSWORD)[(Get-Win32ErrorCode $_.Exception.InnerException.HResult) -eq $ERROR_INVALID_PASSWORD]
Write-CustomError $ErrorCode $X509Cert2FilePath ([Management.Automation.ErrorCategory]::InvalidData)
exit $ErrorCode
}
}
}
# $X509Cert2 is type System.Security.Cryptography.X509Certificates.X509Certificate2
try {
# Get the private key as an array of bytes (BLOB - binary large object)
$RSACng = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($X509Cert2)
$KeyBLOB = $RSACng.Key.Export([Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
}
catch {
# Write an error containing the inner exception's HResult
$ErrorCode = $_.Exception.InnerException.HResult
Write-CustomError $ErrorCode -errorCategory $_.CategoryInfo.Category
exit $ErrorCode
}
# Output single PKCS#8 string containing the private key (base64-encoded)
@"
-----BEGIN PRIVATE KEY-----
{0}
-----END PRIVATE KEY-----
"@ -f [Convert]::ToBase64String($KeyBLOB,[Base64FormattingOptions]::InsertLineBreaks)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment