How to secure passwords with PowerShell
Storing passwords in plain text is bad, but what methods are good? Try one of these approaches, such as secure strings or the SecretManagement module, to encrypt passwords.
Password management is critical, yet often difficult to conquer. In fact, it is difficult enough that there are several tools, platforms and methods that manage passwords for you.
Fortunately, the problems that we must solve in PowerShell are straightforward, but equally as important: We must store and handle passwords in a way that limits their exposure.
Dangers of plaintext passwords
All IT professionals know not to store passwords in plain text, but this especially applies to passwords in a version-controlled repository. Once a password is committed and pushed to Git, it becomes part of Git's version history, and removing it can be complex.
Besides writing plaintext passwords to a disk, an easy and innocent blunder is to store a password as a regular string in PowerShell. It might feel more secure because it is difficult to read PowerShell's memory indirectly, but PowerShell stores string variables in its memory in plain text. This means that anything running on your system could potentially access any string variables in your PowerShell session. The chances are low, but the privileges assigned to a service account running an automated script could be devastating in the wrong hands.
Let's look at an example of a PowerShell script that runs the following commands:
$str = 'Password1!'
$str2 = ConvertTo-SecureString 'Password2!' -AsPlainText -Force
$pw = Read-Host -AsSecureString
Using the Windows Debugger, we can either analyze the memory of the process live, or through a memory dump tool, such as ProcDump. Through that analysis, we can find the string variables and see the value of $str, as Figure 1 shows.
If we use the debugger to look through PowerShell's HistoryInfo object, we can also find the value of $str2, seen in Figure 2.
While a full explanation of how to retrieve this information is out of scope for this tutorial, know that analyzing PowerShell's memory can expose plaintext strings.
Using a secure string, as seen in line 3 of Figure 2, prevents the text from being read from memory. Avoid handling the password in a way that could expose it before or after it becomes a secure string.
Use secure strings
Secure strings are often overlooked due to their complexity, so let's briefly cover how to use them.
The script from the first example contains two different secure string lines. The first covers how to convert a normal string into a secure string using ConvertTo-SecureString:
$str2 = ConvertTo-SecureString 'Password2!' -AsPlainText -Force
The second shows how to use Read-Host to automatically convert input into a secure string:
$pw = Read-Host -AsSecureString
This string also obscures the text as you type it, as Figure 3 shows.
With a secure string, we can use a password securely, but it only works with cmdlets and functions that support secure strings. Occasionally, you might need to convert the secure string back to plain text, which you can do with the pscredential type:
[pscredential]::new('user',$pw).GetNetworkCredential().Password
Securely store passwords on a Windows disk
In Windows PowerShell, use the ConvertFrom-SecureString cmdlet to convert a secure string into an encrypted plaintext string that can be written to disk and used later:
$pw | ConvertFrom-SecureString
The output will look something like Figure 4.
You can pipe this output directly into a file and know it is encrypted. If the -Key parameter is not specified, then the Windows Data Protection API secures the string. This string can only be decrypted by the same user on the same machine.
Choose to specify a custom encryption key with the -Key parameter:
$key = 0..255 | Get-Random -Count 32 | %{[byte]$_}
$pw | ConvertFrom-SecureString -Key $key
You should store the key separately from the plaintext encrypted password.
To read the string, use the ConvertFrom-SecureString key:
$encStr = Get-Content .\password.txt
$encStr | ConvertFrom-SecureString -Key $key
Securely store passwords on a cross-platform disk
Because the method of storing passwords covered in the last section is dependent on the Windows Data Protection API, it is Windows specific. But Microsoft has developed a module to handle passwords compatible with both Windows PowerShell and PowerShell 6+ on all platforms: the SecretManagement module.
The SecretManagement module enables you to interface with encrypted storage for secrets in a standardized manner. The SecretManagement module does not store secrets; it only provides an interface to a secrets vault.
For secret storage, Microsoft provides SecretStore -- a basic but effective storage option -- or connects with your favorite secrets vault, such as KeePass, LastPass or 1Password. Be aware that these are community-developed modules, not official releases from Microsoft or the respective companies.
To initialize a vault with the SecretManagement module, use the Register-SecretVault cmdlet:
Register-SecretVault -Name FirstVault -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
Specify which module to use as the vault. In this case, I'm using the Microsoft-developed SecretStore module.
When creating a vault, you will not be prompted for a password to secure the vault until you first create a secret using the Set-Secret cmdlet:
Set-Secret -Name FirstPassword -Secret "Password1!"
Then it will prompt you to secure the vault with a password.
To retrieve the password, use the Get-Secret cmdlet:
Get-Secret -Name FirstPassword
By default, this will return the password as a secure string. However, if you need the password in plain text, use the -AsPlainText parameter.