Generate an AES-256 key Encrypt using a self-signed certificate
Storing a 256-bit key securely in the Windows Certificate Store is not directly supported in the same way certificates and private keys are. The Certificate Store is designed to store X.509 certificates, not arbitrary binary secrets like AES keys.
However, you can embed the AES key as a custom extension or encrypt it using an RSA public key from a certificate, then store that certificate in the Windows Certificate Store.
Below we generate an AES-256 key, encrypt it using a self-signed certificate’s public key, and store the certificate (with private key) in the CurrentUser\My store. The encrypted AES key can be stored in the registry, file, or database.
✅ Function: New-AesKeyWithCert
This function:
- Creates a new AES 256-bit key.
- Creates a self-signed certificate with an RSA key pair.
- Encrypts the AES key using the certificate’s public key.
- Saves the certificate in the CurrentUser\My store.
- Returns both the certificate thumbprint and encrypted key.
function New-AesKeyWithCert {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, HelpMessage = "Name of the certificate subject")]
[string]$CertName,
[Parameter(Mandatory = $false, HelpMessage = "Validity in years")]
[int]$ValidYears = 5
)
# Generate 256-bit AES key
$aesKey = New-Object byte[] 32
[System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($aesKey)
# Create a self-signed certificate with an RSA key
$cert = New-SelfSignedCertificate `
-Subject "CN=$CertName" `
-KeyExportPolicy Exportable `
-KeySpec KeyExchange `
-KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-NotAfter (Get-Date).AddYears($ValidYears)
if (-not $cert) {
throw "Failed to create self-signed certificate."
}
# Get the public key to encrypt the AES key
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($cert)
$encryptedKey = $rsa.Encrypt($aesKey, [Security.Cryptography.RSAEncryptionPadding]::Pkcs1)
# Return thumbprint and encrypted key as base64
return [pscustomobject]@{
CertificateThumbprint = $cert.Thumbprint
EncryptedAesKeyBase64 = [Convert]::ToBase64String($encryptedKey)
}
}
🔐 Decrypting Later
To decrypt the AES key:
function Get-AesKeyFromCert {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, HelpMessage = "Certificate thumbprint")]
[string]$Thumbprint,
[Parameter(Mandatory = $true, HelpMessage = "Encrypted key as base64 string")]
[string]$EncryptedKeyBase64
)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", "CurrentUser"
$store.Open("ReadOnly")
$cert = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint }
$store.Close()
if (-not $cert) {
throw "Certificate not found with thumbprint $Thumbprint"
}
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$encryptedKey = [Convert]::FromBase64String($EncryptedKeyBase64)
return $rsa.Decrypt($encryptedKey, [Security.Cryptography.RSAEncryptionPadding]::Pkcs1)
}
🧪 Example Use:
# Generate and store
$result = New-AesKeyWithCert -CertName "MyAesKeyCert"
# Store encrypted key somewhere (e.g., registry or file)
$thumbprint = $result.CertificateThumbprint
$encryptedKeyBase64 = $result.EncryptedAesKeyBase64
# Later, decrypt it
$aesKey = Get-AesKeyFromCert -Thumbprint $thumbprint -EncryptedKeyBase64 $encryptedKeyBase64
Complete Encryption and Decryption Flow
Here’s a complete example where:
- A 256-bit AES key is generated.
- A self-signed certificate is created and stored in the Windows Certificate Store.
- The AES key is encrypted with the certificate’s public key.
- The encrypted AES key and the certificate (public part only) are exported to a
.json
file. - Another machine can use that JSON to import the public cert and decrypt the AES key if it has the private key (i.e., the certificate including private key is installed there).
✅ Part 1: Generate Key and Save to JSON
function Save-AesKeyToJson {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$CertName,
[Parameter(Mandatory = $true)]
[string]$OutputFile
)
# Generate AES-256 key
$aesKey = New-Object byte[] 32
[System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($aesKey)
# Create self-signed cert
$cert = New-SelfSignedCertificate `
-Subject "CN=$CertName" `
-KeyExportPolicy Exportable `
-KeySpec KeyExchange `
-KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-NotAfter (Get-Date).AddYears(5)
# Encrypt AES key with cert's public key
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($cert)
$encryptedKey = $rsa.Encrypt($aesKey, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)
# Export cert (public only)
$publicCert = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
$certBase64 = [Convert]::ToBase64String($publicCert)
$encryptedKeyBase64 = [Convert]::ToBase64String($encryptedKey)
# Save JSON file
$json = @{
CertificateSubject = $cert.Subject
CertificateThumbprint = $cert.Thumbprint
PublicCertificate = $certBase64
EncryptedAesKey = $encryptedKeyBase64
} | ConvertTo-Json -Depth 3
Set-Content -Path $OutputFile -Value $json -Encoding UTF8
Write-Host "Key and cert saved to $OutputFile"
}
✅ Part 2: Decrypt the AES Key from the JSON File
This function reads the JSON file, loads the cert from the store (must include private key), and decrypts the AES key.
function Load-AesKeyFromJson {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$JsonFile
)
# Load JSON content
$data = Get-Content -Path $JsonFile | ConvertFrom-Json
$thumbprint = $data.CertificateThumbprint
$encryptedKey = [Convert]::FromBase64String($data.EncryptedAesKey)
# Load certificate from CurrentUser\My (must have private key)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", "CurrentUser"
$store.Open("ReadOnly")
$cert = $store.Certificates | Where-Object { $_.Thumbprint -eq $thumbprint }
$store.Close()
if (-not $cert.HasPrivateKey) {
throw "Certificate does not contain a private key. Import the full cert with private key (.pfx)."
}
# Decrypt AES key
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$aesKey = $rsa.Decrypt($encryptedKey, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)
return $aesKey
}
✅ Example Usage
# On machine A
Save-AesKeyToJson -CertName "MySecureCert" -OutputFile "C:\secure\aeskey.json"
# Export private cert to .pfx (manually or via script) if needed:
# Export-PfxCertificate -Cert "Cert:\CurrentUser\My\<thumbprint>" -FilePath C:\secure\cert.pfx -Password (ConvertTo-SecureString -String 'MyPassword' -AsPlainText -Force)
# On machine B
# Import cert.pfx first (needs private key)
# Import-PfxCertificate -FilePath C:\secure\cert.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String 'MyPassword' -AsPlainText -Force)
# Load AES key from JSON
$aesKey = Load-AesKeyFromJson -JsonFile "C:\secure\aeskey.json"
🔐 Notes:
- Only the machine with the certificate + private key can decrypt the AES key.
- You can share the JSON file freely (it contains no private info).
- The PFX file must be protected with a strong password and imported securely.