Encrypting Secrets using PowerShell and AWS KMS

AWS Key Management Service (KMS) is an Amazon managed service that makes it easy for you to create and control encryption keys that you can then use to encrypt data.  A lot of the AWS services natively integrate with KMS e.g. S3, but I wanted to use a KMS key to encrypt a secret (e.g. a password) that I could store inside a configuration file and decrypt it when required.

To do this I created a two PowerShell functions, one for encryption and one for decryption, that I can embed in scripts.  The encryption function will securely transfer your plaintext to KMS, KMS will encrypt the data and return an encrypted memory stream which I convert to a base64 string to make it easy to store in text/XML/JSON.

The decrypt function takes a previously encrypted base64 string, converts and sends it to KMS to decrypt (note you don’t have to tell KMS which key is required to decrypt) and KMS returns a plaintext memory stream which I convert back to a UTF8 encoded string.

I generally use these functions during userdata execution (boot time) on an AWS EC2 instance to decrypt secrets that I need to configure the instance and/or applications, but you could use this on any windows machine.  To support the use of IAM Roles on EC2 instances, I have made the access/secret key parameters optional i.e. if you don’t pass an access/secret key the function will attempt to use the privileges provided by the IAM role applied to the EC2 instance, assuming you are running the function on EC2.

Function to Encrypt

function Invoke-KMSEncryptText
(
	[Parameter(Mandatory=$true,Position=1,HelpMessage='PlainText to Encrypt')]
	[string]$plainText,
	[Parameter(Mandatory=$true,Position=2,HelpMessage='GUID of Encryption Key in KMS')]
	[string]$keyID,
	[Parameter(Mandatory=$true,Position=3)]
	[string]$region,
	[Parameter(Position=4)]
	[string]$AccessKey,
	[Parameter(Position=5)]
	[string]$SecretKey
)
{
	# memory stream
	[byte[]]$byteArray = [System.Text.Encoding]::UTF8.GetBytes($plainText)
	$memoryStream = New-Object System.IO.MemoryStream($byteArray,0,$byteArray.Length)
	# splat
	$splat = @{Plaintext=$memoryStream; KeyId=$keyID; Region=$Region;}
	if(![string]::IsNullOrEmpty($AccessKey)){$splat += @{AccessKey=$AccessKey;}}
	if(![string]::IsNullOrEmpty($SecretKey)){$splat += @{SecretKey=$SecretKey;}}
	# encrypt
	$encryptedMemoryStream = Invoke-KMSEncrypt @splat
	$base64encrypted = [System.Convert]::ToBase64String($encryptedMemoryStream.CiphertextBlob.ToArray())
	return $base64encrypted
}

Function to Decrypt

function Invoke-KMSDecryptText
(
	[Parameter(Mandatory=$true,Position=1,HelpMessage='CipherText base64 string to decrypt')]
	[string]$cipherText,
	[Parameter(Mandatory=$true,Position=2)]
	[string]$region,
	[Parameter(Position=3)]
	[string]$AccessKey,
	[Parameter(Position=4)]
	[string]$SecretKey
)
{
	# memory stream
	$encryptedBytes = [System.Convert]::FromBase64String($cipherText)
	$encryptedMemoryStreamToDecrypt = New-Object System.IO.MemoryStream($encryptedBytes,0,$encryptedBytes.Length)
	# splat
	$splat = @{CiphertextBlob=$encryptedMemoryStreamToDecrypt; Region=$Region;}
	if(![string]::IsNullOrEmpty($AccessKey)){$splat += @{AccessKey=$AccessKey;}}
	if(![string]::IsNullOrEmpty($SecretKey)){$splat += @{SecretKey=$SecretKey;}}
	# decrypt
	$decryptedMemoryStream = Invoke-KMSDecrypt @splat
	$plainText = [System.Text.Encoding]::UTF8.GetString($decryptedMemoryStream.Plaintext.ToArray())
	return $plainText
}

Below is some sample code that makes use of the functions, simply fill in the access/secret keys, the KMS Master key you want to use for encryption and the region where the key is stored.  Obviously you should consider handling your plaintext more securely than I am here, but this serves as a simple test.

Import-Module awspowershell
# set your credentials to access AWS, key you want to encrypt with, and the region the key is stored
$AccessKey = ''
$SecretKey = ''
$Region = 'eu-west-1'
$keyID = ''
$plainText = 'Secret'

# Encrypt some plain text and write to host
$cipherText = Invoke-KMSEncryptText -plainText $plainText -keyID $keyID -Region $Region -AccessKey $AccessKey -SecretKey $SecretKey
Write-host $cipherText

# Decrypt the cipher text and write to host
$plainText = Invoke-KMSDecryptText -cipherText $cipherText -Region $Region -AccessKey $AccessKey -SecretKey $SecretKey
Write-host $plainText

THIS POSTING AND CODE RELATED TO IT ARE PROVIDED “AS IS” AND INFERS NO WARRANTIES OR RIGHTS, USE AT YOUR OWN RISK