Search code examples
aws-powershellamazon-kms

How to encrypt data using KMS key in AWS Powershell script


I am trying to encrypt a text using AWS KMS and creating a powershell script. So I used New-KMSDataKey to encrypt my KMS master key which in output returns plaintextDataKey and ciphertextblob.

Now I am using plaintextDataKey to encrypt my plaintext using Invoke-KMSEncrypt but I get Invalid Operation error as shown below:

enter image description here

Below is my script:

param([string]$zonesecret, [string]$KMSKey, [string]$Keyspec, [string]$region= 'us-east-1', [string]$AccessKey, [string]$SecretKey)

# splat
$splat = @{KeyId=$KMSKey; KeySpec=$Keyspec; Region=$region}
# generate a data key 
$datakey = New-KMSDataKey @splat

$plaintextDataKey = [Convert]::ToBase64String($datakey.Plaintext.ToArray())
$encryptedDataKey = [Convert]::ToBase64String($datakey.CiphertextBlob.ToArray())
Write-Host $plaintextDataKey
Write-Host $encryptedDataKey

#encrypt using aes-256; pass zonesecret and plaintextDataKey
# memory stream
[byte[]]$byteArray = [System.Text.Encoding]::UTF8.GetBytes($zonesecret)
$memoryStream = New-Object System.IO.MemoryStream($byteArray,0,$byteArray.Length)

$splat = @{Plaintext=$memoryStream; KeyId=$plaintextDataKey; Region=$region;}
if(![string]::IsNullOrEmpty($AccessKey)){$splat += @{AccessKey=$AccessKey;}}
if(![string]::IsNullOrEmpty($SecretKey)){$splat += @{SecretKey=$SecretKey;}}

# encrypt
**$encryptedMemoryStream = Invoke-KMSEncrypt @splat** # ERROR: 
Write-Host $encryptedMemoryStream

$base64encrypted = [System.Convert]::ToBase64String($encryptedMemoryStream.CiphertextBlob.ToArray())
Write-Host $base64encrypted

What can I do to make it right? Am I doing anything wrong here? There is no other cmdlets here to encrypt data: http://docs.aws.amazon.com/powershell/latest/reference/Index.html

Could anyone please help here? How can I use the above plaintext data key to encrypt my content?


Solution

  • The code presented has many of the right bones in it to be a part of this animal, but it has several issues that prevent it from working, including missing some of the concepts underpinning KMS. Using data keys is a great way to work with KMS, but when you do that, you must understand that you need to rely on your host system's ability to encrypt and decrypt the data with the keys provided.

    KMS Data Keys provide envelope encryption support and the encryption and decryption that KMS provides is for the key itself, not your data. When using data keys, you must use the plaintext key that the KMS provides to encrypt your data, and then store the ciphertext version of the key that you were provided alongside the data. When it is time to decrypt your ciphertext, you use KMS to decrypt the data key and then use the plaintext derived from the data key ciphertext key as the key in your AES decryption.

    Here's an example of a test run from working pair of scripts that I derived from the code you provided, with the KMS key ID scrubbed:

    PS C:\Users\Administrator> $kmskey =  'abcdef01-2345-6789-0123-0123456789ab'
    PS C:\Users\Administrator> ./encrypt.ps1 "This is a test" -KMSKey $kmskey | ConvertTo-Json > message.json
    PS C:\Users\Administrator> type message.json
    {
        "encryptedDataKey":      "AQEDAHix3RkObJxNv8rJGn2Oyy0bRR9GOvSOFTHR2OQ6SBt76wAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDGui8Ycxf+XoJkAkuQIBEIA7R6eiM6PREoJdnaNA5gaeZfcSA3fC3UlRYGE6Epo96U+SqYPYzyXKOEyqB+1+3pCHz2zgZZlbcgzThrs=",
        "ciphertext":  "76492d1116743f0423413b16050a5345MgB8AGwANgBEAEwAaQB5AFQAOQByAGgAYgBPAGcAagBKAGIAQQBBAEwAQgBkAEEAPQA9AHwAMgAzADIAYgA0AGEAYgBlADgAMAA5AGQAZABkADEAOQBlADkAYgBjADgAZgA2ADgAMAA0ADgAZABhADQANQA5ADYAMABiAGIAYQAxADQANABiADAAOAA2ADYANgBlAGYANwAxADkANQA2ADEAMgBjAGEANQBjADAAYgBjAGMANAA=\r\n"
    }
    PS C:\Users\Administrator> ConvertFrom-JSON $(Get-Content .\message.json | Out-String) | .\decrypt.ps1 -KMSKey $kmskey
    
    plaintext
    ---------
    This is a test
    
    PS C:\Users\Administrator> ./encrypt.ps1 "Super Secret Stuff" -KMSKey $kmskey | .\decrypt.ps1 -KMSKey $kmskey
    
    plaintext
    ---------
    Super Secret Stuff
    

    And here are the encrypt and decrypt scripts:

    encrypt.ps1

    param(
        [Parameter(Mandatory=$True)]
        [string]$secret,
    
        [Parameter(Mandatory=$True)]
        [string]$KMSKey,
    
        [string]$Keyspec = 'AES_256',
    
        [string]$region = 'us-east-1'
    )
    
    # generate a data key
    $datakey = New-KMSDataKey -KeyId $KMSKey -KeySpec $Keyspec -Region $region
    
    [byte[]]$plaintextDataKey = $datakey.Plaintext.ToArray()
    [byte[]]$encryptedDataKey = $datakey.CiphertextBlob.ToArray()
    
    # Encrypt using AES using Powershell's SecureString facilities
    # Any AES encryption method would do, this is just the most convenient
    # way to do this from Powershell.
    #
    # Note that trying to use the Invoke-KMSEncrypt method is not what you want
    # to do when using Data Keys and envelope encryption.
    $encrypted = ConvertTo-SecureString $secret -AsPlainText -Force `
        | ConvertFrom-SecureString -key $plaintextDataKey `
        | Out-String
    
    # Thanks to http://stackoverflow.com/a/24778625/424301 
    # for the tip on using psobject return values and
    # ValueFromPipelineByPropertyName parameters (see decrypt.ps1)
    return New-Object psobject -property @{ 
        "ciphertext" = $encrypted;
        "encryptedDataKey" = $([Convert]::ToBase64String($encryptedDataKey))
    }
    

    decrypt.ps1

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,
            Position=0,
            ValueFromPipelineByPropertyName=$true)]
        [string]$ciphertext,
    
        [Parameter(Mandatory=$true,
            Position=1,
            ValueFromPipelineByPropertyName=$true)]
        [string]$encryptedDataKey,
    
        [Parameter(Mandatory=$true,
            Position=2,
            ValueFromPipelineByPropertyName=$true)]
        [string]$KMSKey
    )
    
    [byte[]]$bytes = $([Convert]::FromBase64String($encryptedDataKey))
    $stream = new-object System.IO.MemoryStream (,$bytes)
    
    # decrypt the data key 
    $response = Invoke-KMSDecrypt -CiphertextBlob $stream
    if ($response.HttpStatusCode -eq 200) {
        $dataKey = $response.Plaintext.ToArray()
    } else {
        throw "KMS data key decrypt failed: $(ConvertTo-Json $response)"
    }
    # Now AES decrypt the ciphertext and emit the plaintext
    $secureString = ConvertTo-SecureString $ciphertext -key $dataKey
    $plaintext =  [Runtime.InteropServices.Marshal]::PtrToStringAuto( `
        [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString))
    return  New-Object psobject -property @{
        "plaintext" = $plaintext
    }