Passwords in PowerShell

Sometimes you have to use passwords in PowerShell. For example if you want to shut down a remote computer and have to authorize with another user than the current. You can prompt for the username and password while running the script with Get-Credential:

1
2
$Credential = Get-Credential
Stop-Computer -Computer 192.168.2.10 -Credential $Credential -WhatIf

You can also pre-populate the username. Instead of username and password only the password has to be provided:

1
2
$Credential = Get-Credential -Credential "Domain\User"
Stop-Computer -Computer 192.168.2.10 -Credential $Credential -WhatIf

But what if you write a script which has to run without user interaction (e.g. a Nagios plugin or scheduled task)? You can of course also specify the password as plain text within the script. Before you can use it you have to convert it to a SecureString:

1
2
3
$SecurePassword = ConvertTo-SecureString "SecurePassword" -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential("Domain\User", $SecurePassword)
Stop-Computer -Computer 192.168.2.10 -Credential $Credential -WhatIf

But using plain text passwords in scripts is highly unsecure and should always be avoided. Everyone with access to the script can see the password. Managing such a script in a public or semi-public code repository (e.g. GitHub) is a high security risk. A simple search could reveal your password.

You can specify the password with the help of a parameter. But nevertheless this way the password is also saved in plain text in another file (e.g. the definition of a Nagios check) or in the configuration of your scheduled task:

1
2
3
4
5
6
7
8
Param(
    [Parameter(Mandatory=$True)]
    [string]$Password
)

$SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential("Domain\User", $SecurePassword)
Stop-Computer -Computer 192.168.2.10 -Credential $Credential -WhatIf

Instead you can save your password as an encrypted string to a file (password.txt in the example).

1
2
3
$SecurePassword = Read-Host -AsSecureString
$EncryptedPassword = ConvertFrom-SecureString -SecureString $SecurePassword
Set-Content -Path "password.txt" -Value $EncryptedPassword

Now you can read the password from this file every time you run the script:

1
2
3
4
$EncryptedPassword = Get-Content -Path "password.txt"
$SecurePassword = ConvertTo-SecureString -String $EncryptedPassword
$Credentials = New-Object System.Management.Automation.PSCredential "Domain\User", $SecurePassword
Stop-Computer -Computer 192.168.2.10 -Credential $Credential -WhatIf

PowerShell uses Windows Data Protection API (DPAPI) for encryption of the password. Because of this the encrypted password is only valid for the windows user and host used during generation of the file. The encrypted password is also no longer valid if the password of the windows user is changed. To have an independent encrypted password you have to use the parameter Key or SecureKey. Key uses a ByteArray and SecureKey a SecureSring as Key to encrypt your password. In both cases the key is saved to an additional file (key.txt in the example).

Parameter Key

One-time generation of a random Key (ByteArray) in key.txt and encrypting a password to password.txt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Function RandomByte {
     $RandomByte = @()
     For ($i=1; $i -le 16; $i++) {
     [Byte]$RByte = Get-Random -Minimum 0 -Maximum 256
     $RandomByte += $RByte
     }
     $RandomByte
}
$Key = RandomByte
$Key | out-file "key.txt"
$SecurePassword = ConvertTo-SecureString -String "SecurePassword" -AsPlainText -Force
$EncryptedPassword = ConvertFrom-SecureString -SecureString $SecurePassword -Key $Key
Set-Content -Path "password.txt" -Value $EncryptedPassword

Reading the saved password by getting the key and encrpyted password from the files key.txt and password.txt:

1
2
3
4
$EncryptedPassword = Get-Content -Path "password.txt"
$Key = Get-Content -Path "key.txt"
$SecurePassword = ConvertTo-SecureString -String $EncryptedPassword -Key $Key
$Credentials = New-Object System.Management.Automation.PSCredential "User", $SecurePassword

Parameter SecureKey

One-time generation of a random Key (SecureString) in key.txt and encrypting a password to password.txt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Function RandomString {
     $RandomString = ""
     For ($i=1; $i -le 16; $i++) {
     [Char]$RChar = Get-Random -Minimum 33 -Maximum 127
     $RandomString += $RChar
     }
     $RandomString
}
$KeyString = RandomString
Set-Content -Path "key.txt" -Value $KeyString
$SecurePassword = ConvertTo-SecureString -String "SecurePassword" -AsPlainText -Force
$Key = ConvertTo-SecureString -String $KeyString -AsPlainText -Force
$EncryptedPassword = ConvertFrom-SecureString -SecureString $SecurePassword -SecureKey $Key
Set-Content -Path "password.txt" -Value $EncryptedPassword

Reading the saved password by getting the key and encrpyted password from the files key.txt and password.txt:

1
2
3
4
5
$EncryptedPassword = Get-Content -Path "password.txt"
$KeyString = Get-Content -Path "key.txt"
$Key = ConvertTo-SecureString -String $KeyString -AsPlainText -Force
$SecurePassword = ConvertTo-SecureString -String $EncryptedPassword -SecureKey $Key
$Credentials = New-Object System.Management.Automation.PSCredential "User", $SecurePassword

Conclusion

If possible you should ask for the password while running your script with Get-Credential or Read-Host. If you have to automate the script and only one user and host is used you can save the password as SecureString to a file. Otherwise the key used to encrypt the password has also be saved to a file and used with -Key or -SecureKey.

Although the password is not saved in plain text competent users might decrypt your passwords. Make sure to limit access to the password files with additional measures as NTFS permissions.