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:
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:
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
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!
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
LikeLike