The problem
It’s impossible to work in IT without passwords. One of the first things any user must do when he/she logs on to the network is to change his/her password. Together with the username, this credential is your identity on the network. Not only do users have credentials also the so-called service accounts. Most of the time these are just regular user accounts used to run services or scheduled tasks. Very often these accounts are granted local admin rights. Mostly because “it’s easy and it works” or because “it’s a requirement for the software” … The result is that the password of these accounts will probably never change, and they will probably never be deleted. This warrants a debate on its own.
We need to protect the credentials of these accounts. We also need to automate things. This means we might run into a situation where we need to log in as the service account in a script. There was a time where it was normal to find passwords written in clear text in scripts like this
$Password = "MyVeryHardToGuessPassword"
This is unacceptable, it never was. Luckily there are a few possibilities in Powershell to work with secrets securely.
Ask for the credential
The easiest way to work with a credential that you need somewhere in your script is to simply ask for it at the beginning of the script.
$Credential = Get-Credential
Notice that when you access $Credential
you’re able to see the username, but not the password. It only displays System.Security.SecureString
. This is because the password is now stored as a secure string.
This works well when we start the script manually, but what about scheduled scripts?
Store the secret on disk
We can store the password encrypted on disk. Powershell uses the Windows Data Protection API - DPAPI to encrypt/decrypt secrets. This is actually quite easy to use.
Encrypting plain text
ConvertTo-SecureString "MyVeryHardToGuessPassword" -AsPlainText -Force | ConvertFrom-SecureString | Out-File "C:\data\Password.enc"
Encrypting password from credential
($Credential).Password | ConvertFrom-SecureString | Out-File "C:\data\Password.enc"
These will create a file C:\data\Password.enc
with an encrypted string.
Note: Only the user who created this file can decrypt the file to use the password. This is because DPAPI uses the credentials of the current logged in user to encrypt the secret.
Retrieve the secret from disk
The secret is now stored on disk. But what good would it be if we’re not able to retrieve it?
Create SecureString
$Password = Get-Content "C:\data\Password.enc" | ConvertTo-SecureString
You might need to retrieve the unsecured password and not work with the secure string in some cases. This will display the unsecured password.
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$UnsecurePassword
Create Credential
When the password is stored on disk and you need to create a credential simply create it like this.
$Password = Get-Content "C:\data\Password.enc" | ConvertTo-SecureString
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $Password
Make the stored password available for multiple users
As stated before, the encrypted password can only be decrypted by the user how encrypted in the first place. This can become a problem when you need to share the password between machines or users. It turns out that we can pass a key in the form of a byte array to ConvertTo-SecureString
[byte[]]$Key = (1..16)
$Password = "MyVeryHardToGuessPassword" | ConvertTo-SecureString -AsPlainText -Force
$Password | ConvertFrom-SecureString -Key $Key | Out-File "C:\data\Password.enc"
- We create a 32 bit key with
[byte[]]$Key = (1..16)
. (In this example, the key obviously isn’t random.) - We convert the plaintext to a SecureString
- We encrypt to file with the key
Or better yet, never show the password and use Get-Credential
(Get-Credential).Password | ConvertFrom-SecureString -Key $Key | Out-File "C:\data\Password.enc"
To retrieve, simply pass the key
$Password = Get-Content "C:\data\Password.enc" | ConvertTo-SecureString -Key $Key
Store the key
To make this usable we need to use a random key and store it in a location where we can control access to the key. We could store it on a share and set NTFS permissions on the key file.
$KeyFile = "\\Server1\Share\PowerShell.key"
$Key = New-Object Byte[] 16
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key)
$Key | out-file $KeyFile
This will create a file containing our key. Protect this file. Anyone with access to this file can decrypt the files encrypted with the key it contains.
Use it
Below an example of how to use a stored encrypted password with a shared key file.
# Set the variables holding the location of the key and the password file
$KeyFile = "\\Server1\Share\PowerShell.key"
$PasswordFile = "\\Server1\Share\Password.enc"
# Create the key and save to file. Remember to protect this file.
$Key = New-Object Byte[] 16
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key)
$Key | out-file $KeyFile
# Save the password to file with the key
$Key = Get-Content $KeyFile
# $Password = "MyVeryHardToGuessPassword" | ConvertTo-SecureString -AsPlainText -Force
# $Password | ConvertFrom-SecureString -Key $Key | Out-File $PasswordFile
(Get-Credential).Password | ConvertFrom-SecureString -Key $Key | Out-File $PasswordFile
# Retrieve the password with the key
$Key = Get-Content $KeyFile
$Password = Get-Content $PasswordFile | ConvertTo-SecureString -Key $Key
# Show the password in clear text
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
Write-Host $UnsecurePassword
# Create a credential
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $Password
Final thoughts
I honestly hope that this post helps you no longer to store secrets in clear text in scripts anymore. I know there are other solutions to this problem for example using Azure key vaults or real Service Accounts. Maybe I’ll create a post about that in the future.