I have been trying to use PowerShell to encrypt an RSA private key in the same way versions of Openssl below 1.1.0 did it.
Using the excelent code from rashadrivera.com I can extract the private key from a pfx file using PowerShell. What I would then like to do is encrypt the private key using the same password key derivation method as openssl. thanks to mti2935 for such a great explanation of openssl legacy key derivation EVP_BytesToKey.
I understand the method here is considered obsolete, so this is a purely academic exercise to try and achieve the same thing in PowerShell.
The following PowerShell function can create an encrypted private key in pem format but openssl cannot decrypt it. It asks for the password, then fails.
openssl rsa -check -in .\encryptedprivkey.pem
Here is the powershell function
function Export-PrivateKeyPemEncrypted([System.Security.Cryptography.X509Certificates.X509Certificate2]$pfx, [System.String]$outputPath) {
# Process RSA key
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($pfx)
$rsaCng = ([System.Security.Cryptography.RSACng]$rsa)
$keyToEncryptThumbPrint = $rsaCng.Key
$dataToEncrypt = $keyToEncryptThumbPrint.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
# Convert the password into a byte array
$passphrase = "passphrase123456"
[byte[]] $passwordBytes = [Text.Encoding]::UTF8.GetBytes($passphrase) # 16 bytes
# Create an 8 byte salt
# to be added onto the end
# of the password to increase its randomness
$array = @()
for($i=0;$i -lt 8;$i++)
{
$array += [math]::Round($(Get-Random -Minimum 50.1 -Maximum 190.1))
}
[byte[]] $saltBytes = $array
# Create a new instance of the MD5 hashing algorythum
$md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
# Pre openssl V1.1.0 way to create an encryption key
# from a password. This is a bit like the
# obelete standard
# RFC2898 PBKDF1, but not exactly.
# Google EVP_BytesToKey
[byte[]]$firstIteration = $md5.ComputeHash($passwordBytes + $saltBytes) # 16 bytes
[byte[]]$secondIteration = $md5.ComputeHash($firstIteration + $passwordBytes + $saltBytes) # 16 bytes
# Derive the encryption key and Initialization vector
[byte[]]$key = $firstIteration + $secondIteration
[byte[]]$IV = $md5.ComputeHash($secondIteration + $passwordBytes + $saltBytes) # 16 bytes
# Geneate an AES symetrical encryption standard object
# This is the encryption algorythum that will encrypt
# our private key using the key derived from the password above
$aesManaged = New-Object System.Security.Cryptography.AesManaged
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
# Instruct AES object what the key and IV are
$aesManaged.Key = $key
$aesManaged.IV = $IV
$ivAsString = [System.BitConverter]::ToString($IV) -replace "-"
Write-Host " iv = $ivAsString"
Write-Host " key = $([System.BitConverter]::ToString($aesManaged.Key) -replace "-")"
# Now do the actual encypting of the RSA private key
# Data to encrypt must be binary formatted into an array
# of bytes
$encryptor = $aesManaged.CreateEncryptor()
[byte[]] $encryptedData = $encryptor.TransformFinalBlock($dataToEncrypt, 0, $dataToEncrypt.Length)
$aesManaged.Dispose()
# Format the base64 string into lines of 64
# which is what openssl does
$base64CertText = [System.Convert]::ToBase64String($encryptedData) -replace ".{64}", "`$&`r`n"
# Creat a variable to store the encypted key
$out = New-Object String[] -ArgumentList 5
# PEM file. See RFC1421 page 24
# for heading explanations
$out[0] = "-----BEGIN RSA PRIVATE KEY-----"
$out[1] = "Proc-Type: 4,ENCRYPTED"
$out[2] = "DEK-Info: AES-256-CBC,$ivAsString`r`n"
$out[3] = $base64CertText
$out[4] = "-----END RSA PRIVATE KEY-----"
$out | Out-File $outputPath
# this removes CR/LF combination that openssl hates
(Get-Content $outputPath) | Set-Content $outputPath
}
To use the powershell function I first do the following, having already extracted my certificate and private key to a pfx file called mypfx.pfx
$privKeyPasWd = ConvertTo-SecureString -String "passphrase123456" -Force -AsPlainText
$pfxExportOptions = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
$pfxAsCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("C:\Certs\mypfx.pfx", $privKeyPasWd, $pfxExportOptions)
$privateKeyName = "C:\Certs\encryptedprivkey.pem"
Export-PrivateKeyPemEncrypted $pfxAsCertificate $privateKeyName
I notice the pem file created by the PowerShell function is larger in size than the same private key encypted using openssl command
openssl rsa -aes256 -in .\privkey.pem -out .\encryptedprivkey.pem
What is the PowerShell function adding that Openssl does not? Many thanks for any assistance.