Encrypting Credentials In PowerShell Scripts

I have a long-standing dislike of hard-coding credentials in scripts.  In a production environment, it’s never a good idea to leave sensitive account passwords hard-coded in plain text in scripts.  To that end, I’ve developed an easy method in PowerShell to protect sensitive information.

The functions I present below allow you to store usernames and passwords, where the passwords are encrypted, in a form that can be later decrypted inside a script.  By default, only the user account that encrypted the credentials can decrypt them, and only from that same machine.  It all uses native .NET stuff, so you don’t need any third-party stuff to get it working.

Where I find this most useful is for services or scheduled tasks that run as system accounts that execute PowerShell scripts.  You can log into the machine as that service account, encrypt a set of credentials, then when that scheduled task runs as that service account it is able to read them.

Using the export function I show below, you can either export your credentials to an xml file on the file system, or a registry value in the Windows registry.

Here is an example:

First, save the credential to a variable and export it to an xml file:

$cred = Get-Credential username
$cred | Export-PSCredential -Path c:\temp\creds.xml

This outputs the path to the xml file you created with the encrypted credentials:

Export-PSCredential

Alternately, you can export to a registry key instead:

$cred = Get-Credential username
$cred | Export-PSCredential -RegistryPath HKCU:\software\test -Name mycreds

In the registry, you can see your exported credentials:

Export-Registry

https://gist.github.com/BrandonStiff/02cada362bfca007d298b549506f225f.js

The major thing that needs to be understood about this is the encryption key that is used to encrypt these credentials is tied to both the userid used to encrypt them AND the machine you encrypted from.  Unless you specify a keyphrase, you cannot decrypt these credentials as another user or from another machine.  The idea is if you have a script that reads these encrypted credentials, you have to log in as the user the script runs as on the machine the script runs from and encrypt them.  However, as described above, if you provide a keyphrase, you can decrypt them from anywhere as any user.  You just have to somehow protect the keyphrase.

Importing the credentials again is pretty simple:

$cred = Import-PSCredential -Path C:\temp\creds.xml
# OR
$cred = Import-PSCredential -RegistryPath HKCU:\Software\test -Name mycreds

Import-PSCredential

Specifying a keyphrase involves specifying the -KeyPhrase parameter on either the import or export function.

Below is the code.  Simply paste these three functions into your PowerShell session or into your script and away you go.

function Get-EncryptionKey()
{
<#
.SYNOPSIS
Retrieves a 128/192/256-bit encryption key using the given keyphrase.
.PARAMETER KeyPhrase
Specifies the phrase to use to create the 128-bit key.
.PARAMETER Length
Specifies the number of bits to make the length. Use either 128, 192, or 256 bits. Default is 128.
.OUTPUTS
[byte[]]
Returns a 128/192/256-bit (32/48/64-byte) array that represents the keyphrase.
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
[string] $KeyPhrase,
[ValidateSet(128,192,256)] [int] $Length = 128
)
process
{
$enc = [System.Text.Encoding]::UTF8;
$bytes = $Length / 4;
$KeyPhrase = $KeyPhrase.PadRight($bytes, "0").SubString(0,$bytes);
$enc.GetBytes($KeyPhrase);
}
}

function Export-PSCredential
{
<#
.SYNOPSIS
Exports a credential object into an XML file or registry value with an encrypted password. An important note is that the encrypted password can ONLY be read by the user who created the exported file
unless a passphrase is provided.
.PARAMETER Credential
Specifies the Credential to export to a file. Use Get-Credential to supply this.
.PARAMETER Path
Specifies the file to export to. Default is (CurrentDir)\encrypted.xml.
.PARAMETER RegistryPath
Specifies the path to the registry to export the credentials to. Use HKLM and HCKU for HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER respectively. Example: HKCU:\Software\Acme Inc\MyCredentials
.PARAMETER Name
Specifies the name of the registry value to store the credentials under. Only specify with RegistryPath.
.PARAMETER KeyPhrase
Specifies the key phrase to use to encrypt the password. If not specified, then a key derived from the user's account is used. This makes the password only decryptable by the user who encrypted it.
If a key is specified, then anybody with the key can decrypt it.
.EXAMPLE
PS> (Get-Credential bsti) | Export-PSCredential
# Encrypts the credential for username bsti and exports to the current directory as encrypted.xml
.EXAMPLE
PS> (Get-Credential bsti) | Export-PSCredential -Path C:\temp\mycreds.xml
# Encrypts the credential for username bsti and exports to the current directory as encrypted.xml
.EXAMPLE
PS> (Get-Credential bsti) | Export-PSCredential -RegistryPath "HKCU:\Software\Acme Inc\MyCreds" -Name "switch1"
# Encrypts the credential for username bsti and exports to the registry at the given path, under the value switch1.
.EXAMPLE
PS> (Get-Credential bsti) | Export-PSCredential -Path C:\temp\mycreds.xml -KeyPhrase "ThisisMyEncryptionPassword123"
# Encrypts the credential for username bsti and exports it to the filesystem. Anyone with the keyphrase can decrypt it.
.OUTPUTS
Returns the [System.IO.FileInfo] object representing file that was created or the path to the registry key the credentials were exported to.
#>
[CmdletBinding(SupportsShouldProcess=$true,DefaultParameterSetName="filesystem")]
param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[Management.Automation.PSCredential] $Credential,
[Parameter(ParameterSetName="filesystem")]
[ValidateScript({ Test-Path Path (Split-Path Path $_) PathType Container } )]
[string] $Path = $(Join-Path Path (Get-Location) ChildPath "encrypted.xml"),
[Parameter(Mandatory=$true,ParameterSetName="registry")]
[string] $RegistryPath,
[Parameter(Mandatory=$true,ParameterSetName="registry")]
[string] $Name,
[string] $KeyPhrase
)
process
{
foreach ( $cred in $Credential )
{
# Create temporary object to be serialized to disk
$export = "" | Select-Object Username, EncryptedPassword
# Give object a type name which can be identified later
$export.PSObject.TypeNames.Insert(0,"ExportedPSCredential")
$export.Username = $Credential.Username
# Encrypt SecureString password using Data Protection API
# Only the current user account can decrypt this cipher unless a key is specified:
$params = @{}
if ( $KeyPhrase )
{
$params.Add("Key", (Get-EncryptionKey KeyPhrase $KeyPhrase))
}
$export.EncryptedPassword = $Credential.Password | ConvertFrom-SecureString @params
if ( $PSCmdlet.ParameterSetName -ieq "registry" )
{
# Export to registry
# Make sure the registry key exists:
if ( !(Test-Path Path $RegistryPath) )
{
New-Item Path $RegistryPath Force | Out-Null
}
# Set/Update the credential in the registry store:
Set-ItemProperty Path $RegistryPath Name $Name Value ("{0}:{1}" -f $export.UserName, $export.EncryptedPassword) Force
}
else
{
# Export using the Export-Clixml cmdlet
$export | Export-Clixml $Path
# Return FileInfo object referring to saved credentials
Get-Item Path $Path
}
}
}
}

function Import-PSCredential
{
<#
.SYNOPSIS
Imports a credential exported by Export-PSCredential and returns a Credential.
.PARAMETER Path
Specifies one or more files to convert from XML files to credentials.
.PARAMETER RegistryPath
Specifies the path in the registry to look for the encrypted credentials.
.PARAMETER Name
Specifies the registry key the credentials are stored under.
.PARAMETER KeyPhrase
Specifies the key phrase to use to encrypt the password. If not specified, then a key derived from the user's account is used. This makes the password only decryptable by the user who encrypted it.
If a key is specified, then anybody with the key can decrypt it.
.EXAMPLE
Import-PSCredential -Path C:\temp\mycreds.xml
# Retrieves encrypted credenials from the given file.
.EXAMPLE
Get-ChildItem C:\temp\credstore | Import-PSCredential
# Retrieves encrypted credenials from files in the given directory.
.EXAMPLE
Import-PSCredential -RegistryPath "HKCU:\Software\Acme Inc\MyCreds" -Name switch1
# Retrieves encrypted credenials from the registry path: "HKCU:\Software\Acme Inc\MyCreds" Key switch1
.EXAMPLE
Import-PSCredential -Path C:\temp\mycreds.xml -KeyPhrase "test12345"
# Retrieves encrypted credenials from the filesystem. Decrypts them using the given key.
.OUTPUTS
[System.Management.Automation.Credential]
Outputs a credential object representing the cached credentials. Use GetPlainTextPassword() to retrieve the plain text password.
#>
[CmdletBinding(DefaultParameterSetName="filesystem")]
param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="filesystem")]
[ValidateScript({ Test-Path Path $_ PathType Leaf } )] [String[]] $Path,
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="registry")]
[string] $RegistryPath,
[Parameter(Mandatory=$true,ParameterSetName="registry")]
[string] $Name,
[string] $KeyPhrase
)
begin
{
$paths = @()
}
process
{
if ( $PSCmdlet.ParameterSetName -ieq "registry" )
{
$paths += $RegistryPath
}
else
{
$paths += $Path
}
foreach ( $p in $paths )
{
$import = $null
if ( $PSCmdlet.ParameterSetName -ieq "registry" )
{
# Imported from registry:
$import = "" | Select-Object "UserName","EncryptedPassword"
# Make sure the registry key exists:
if ( Test-Path Path $p )
{
$regValue = Get-ItemProperty Path $p | Where-Object { $_.$Name }
if ( $regValue )
{
$credsAsString = (Get-ItemProperty Path $p).$Name
if ( ($credsAsString -split ":").Count -lt 2 )
{
throw ("Credential was stored in an invalid format!")
}
$import.UserName = ($credsAsString -split ":")[0]
$import.EncryptedPassword = ($credsAsString -split ":")[1]
}
}
}
else
{
$fileFullPath = $p
if ( $p -is [System.IO.FileInfo] )
{
$fileFullPath = $p.FullName
}
# Import credential file
$import = Import-Clixml $fileFullPath
}
if ( $import -and $import.UserName -and $import.EncryptedPassword )
{
$userName = $import.Username
# Decrypt the password and store as a SecureString object for safekeeping
try
{
$params = @{};
if ( $KeyPhrase )
{
$params.Add("Key",(Get-EncryptionKey KeyPhrase $KeyPhrase));
}
$securePass = $import.EncryptedPassword | ConvertTo-SecureString ErrorAction Stop @params;
}
catch [System.FormatException]
{
throw ("An invalid encryption key was supplied! If this credential was encrypted with a KeyPhrase, you must use the correct keyphrase to decrypt it!");
}
catch [System.Security.Cryptography.CryptographicException]
{
throw ("Invalid encryption key! If no key is specified, then only the user that exported the credential in file $fileFullPath can retrieve it! Current user $($env:UserDomain)\$($env:UserName) may not have access!");
}
catch
{
throw $_;
}
# Build the new credential object
Get-PSCredential Credential (New-Object System.Management.Automation.PSCredential $userName, $securePass);
}
}
}
}

Note the Get-EncryptionKey function is required for both the import and export functions!

1 thought on “Encrypting Credentials In PowerShell Scripts

  1. BestRosemary

    I see you don’t monetize your website, don’t waste your traffic, you can earn extra bucks every month because you’ve got
    high quality content. If you want to know how to make extra $$$, search for: Ercannou’s essential adsense alternative

    Like

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s