Persisting sensitive information with PowerShell

It often happens that I need to persist a password or another sensitive information strings in a file or database. When it happens I can never recall what was exactly the command I used to do so in the past. That’s why I decided to encapsulate the two operation of encrypting and decrypting a string in a cmdlet so that the next time I can just check my blog post.

A small preface about the operation of encryption. It is based on the ConvertTo-SecureString cmdlet which on its own uses Advanced Encryption Standard (AES) encryption algorithm. Supported key lengths by the AES encryption algorithm in this case are 128, 192, or 256 bits and they do depend on the specified key length.

function Protect-String()
{
    [CmdletBinding()]
    param
    (
        [string][parameter(Mandatory = $true)]$String,
        [string][parameter(Mandatory = $true)]$Key
    )
    BEGIN { }
    PROCESS
    {      
        if (([system.Text.Encoding]::Unicode).GetByteCount($Key) * 8 -notin 128,192,256)
        {
            throw "Given encription key has an invalid lenght. The specified key must have a length of 128, 192, or 256 bits."
        }

        $secureKey = ConvertTo-SecureString -String $Key -AsPlainText -Force
        
        return ConvertTo-SecureString $String -AsPlainText -Force | ConvertFrom-SecureString -SecureKey $secureKey
    }
    END { }
}

As you can see, there are two required parameters, string that you are trying to encrypt and the key to use to encrypt it. As mentioned above, specified key must have a length of 128, 192, or 256 bits. This translate in a string with length respectively equal to 8, 12 or 16 chars. The calculation is simple, strings inside PowerShell are represented as 16-bit Unicode, instances of .NET’s System.String class, thus 16 bits per character. Knowing this, the maths is easy.
For record, if we haven’t specified any key, the Windows Data Protection API (DPAPI) would be used to encrypt the standard string representation and we wouldn’t be capable of decrypting our string on a different computer.
After we invoke our cmdlet, we will get back the encrypted string. We can then persist that information safely in, at example, our configuration file or a database field.

Once we need to read our value back we can use the following cmdlet:

function Unprotect-String()
{
    [CmdletBinding()]
    param
    (
        [string][parameter(Mandatory = $true)]$String,
        [string][parameter(Mandatory = $true)]$Key
    )
    BEGIN { }
    PROCESS
    {
        if (([system.Text.Encoding]::Unicode).GetByteCount($Key) * 8 -notin 128,192,256)
        {
            throw "Given encription key has an invalid lenght. The specified key must have a length of 128, 192, or 256 bits."
        }

        $secureKey = ConvertTo-SecureString -String $Key -AsPlainText -Force
        $secureString = ConvertTo-SecureString $String -SecureKey $secureKey

        $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "dummy", $secureString

        return $credential.GetNetworkCredential().Password
    }
    END { }
}

Passing in the encrypted string and the key that should be used to decrypt the information, this cmdlet will return the decrypted string.

Following an example:

$password = "My strong super password"
$key = '1234567890123456'

$encryptedString = Protect-String $password $key

Unprotect-String $encryptedString $key

You can expect to see outputted console the “My strong super password”.

That’s all folks. Keep your sensitive information safe!