How to Encrypt Passwords in PowerShell

Save to My DOJO

How to Encrypt Passwords in PowerShell

As an MSP, managing passwords in PowerShell scripts can be a dicey task. There is always a risk that someone may find the password by simply taking a peak at your code. However, there are certain scenarios that call for “storing” a password somewhere and referencing it in a script for authentication. To do this safely you’ll need to encrypt them and you can do this using PowerShell. If you’re new to PowerShell, don’t worry it’s not too difficult!

Why Should I Encrypt Passwords?

Let’s say you need to routinely run a script against a group of non-domain joined servers, or you’d like to allow users to run a specific elevated task but don’t want to give them the elevated credentials. There are a few solutions around this, but there is a proper balance between security and accessibility and it takes some discerning on if a solution is secure enough to be accepted. Luckily with PowerShell, there are a few tricks we can use to hide our passwords, and while they are not 100% secure, it will still reduce the risk by a significant amount depending on the method.

By now it should be common sense that you don’t “hard code” passwords into any sort of script like below:

$password = “MYPASSWORD”

Anyone can easily just open up your script file and read the password. Instead here are 3 more secure ways of passing credentials through to your PowerShell scripts

Using Task Scheduler

This is the easiest method of all. When configuring a task, Task Scheduler allows you to store your account credentials and will execute your specified script using those credentials:

This is useful when running a script that needs access to file shares or any domain authenticated endpoint. However, task scheduler will only store 1 set of credentials and uses the Windows Data Protection API to encrypt/decrypt the password. This method uses the user account login credentials sort of as a “key” to access the stored password. The con to this method is, since the login credentials are being stored locally on the server, the script can only be run on the server that has the credentials cached and the Task Scheduler configured.

Create an Encrypted Password File

Another way we can go about hiding the passwords used in our PowerShell scripts, is by creating an encrypted password file and then referencing that password file in our script. Just like Task Scheduler, this method will encrypt using the Windows Data Protection API, which also means we fall into the same limitations of only being able to access the password file with one account and only on the same device that created the password file. The user login credentials are essentially the “key” to the password file. However, this method allows us to save multiple passwords and reference them in our script.

To get started we will create the password file by inputting the following syntax into a PowerShell console. Also, note that the user account you are using to create the password file is the same account that must be used to open the password file (Windows Data Protection API remember?):

(get-credential).password | ConvertFrom-SecureString | set-content "C:\Passwords\password.txt"

You will get a prompt for the password, input the credentials that you want to save. In our example an encrypted password file will be saved to “C:\passwords\password.txt”:

When we open the file we can see that our credentials are encrypted:

Now, how do we retrieve these credentials? Easy, if we ever need to retrieve these we include the following syntax in our scripts to provide the creds:

$password = Get-Content "C:\Passwords\password.txt" | ConvertTo-SecureString 
$credential = New-Object System.Management.Automation.PsCredential("Luke",$password)

Then just pass $credential to whatever cmdlets need a pscredential to authenticate. If we look at what’s in the $credential variable we can see our username and its encrypted password:

Like I said before, you still fall under the limitation of requiring the same user account to run the script and only on the same machine that you created the password file on. Thankfully there is a 3rd option that allows us to get around this.

Creating a Key File and Password File

With PowerShell, we can generate a 256-bit AES encryption key and use that key to access our password file. First, we input the following syntax to create our key file. You could take this key and put it on a network share and only give specific users access to the key along with the password file. But, in this example we will just save it to C:\passwords with our password file:

$Key = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key)
$Key | out-file C:\passwords\aes.key

If we open the aes.key in notepad we can see our key is now generated:

Now, we create a password file just like above, however, we use the -key parameter to specify that we want to use a key and input the location of the key file. Then we create the password file. In this example we’ll output the password file to our C:\passwords directory:

(get-credential).Password | ConvertFrom-SecureString -key (get-content C:\passwords\aes.key) | set-content "C:\Passwords\password.txt"

Now that we have our password file and our key file. We can simply recall our password from any script by including the following syntax in the script:

$password = Get-Content C:\Passwords\password.txt | ConvertTo-SecureString -Key (Get-Content C:\Passwords\aes.key)
$credential = New-Object System.Management.Automation.PsCredential("Luke",$password)

When we look at the data inside the $credential variable we can see that we have the username and password now.

Wrap-Up

Each method has it’s own pros and cons. However, keep in mind that all of these ways are not 100% foolproof. So, I would not advise doing this with a domain administrator account. Just enough access (JEA) is the way to do it, so I would recommend creating an account with just enough access to do what it needs to do.

How about you? What interesting ways have you used to get around the issue of storing credentials in plaintext? Let us know in the comments section below!

More articles about how MSPs can make the most out of PowerShell in their operations:

Using SFTP

Working with REST APIs

HTML Tables for Reporting

Altaro O365 Backup for MSPs
Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

239 thoughts on "How to Encrypt Passwords in PowerShell"

  • Marcos says:

    Thank for the information provide about Encrypt Passwords

    very clear steps

  • Eric says:

    Very Nice!

  • Casey says:

    I’m curious, how does this provide extra security? If someone can access files and see a plain text pw in a script, they could just as easily access they key and password files and unencrypt them. Or am I misunderstanding something here?

    • That’s a good question! In this solution you would use NTFS permissions to lock down the folder that contains the password file and key so that only the accounts that are given permission to this folder will have the ability to retrieve the password when the script is ran.

      • Mr. Peanut Butter and Jelly Sandwhich says:

        Why not just store the script with the plain text pw in the locked down folder?

        • Adds another layer of security. If the contents of the folder get compromised through the numerous security vulnerabilities out there, then we have another security measure in place.

  • Santosh says:

    If password consist $ it gives error how to handle it?

    • You may be using single quotations somewhere and need to be using Double Quotations to mask the $. If that’s not the case, paste in your code and I’ll take a look at what could be causing the error.

  • Ouida says:

    Magnificent website. Plenty of helpful info here. I am sending
    it to some pals ans also sharing in delicious.

    And naturally, thanks for your effort!

  • BROUSSEY says:

    Thanks for this great information.
    Just one question : how can I use only the password not all the credentials.
    I have a script that doesn’t support -credentials but -u user -p password so I tried this syntax:
    $password = Get-Content C:\Passwords\password.txt | ConvertTo-SecureString -Key (Get-Content C:\Passwords\aes.key)
    and then use this as :
    Invoke-Expression “$Rvtools -u $User -p $password -s $VCenter1 -c $ExportType -d $Directory -f $FileNameSite1”

    but that doesn’t work

    Thanks a lot

    • You’ll want to retrive the credentials and then convert them from “securestring” over to clear text. So instead of $credentials.password you can use: ($credentials.GetNetworkCredential()).Password

      • Andy says:

        I was having getting the $credentials.GetNetworkCredential()).Password to work until I changed it to $credential.GetNetworkCredential()).Password

        My only problem is how to encrypt the username too. With (get-credential).Password it appears to prompt and save both the username and password but in your example you’re still specifying the username in plain text (Luke).

        I’m guessing I’d need: $username = ($credential.GetNetworkCredential()).Username but I don’t know what to put in place of Luke.

        • Hi Andy,

          With a pscredential object, the username will still be in plain text, only the password is in secure string format. So no need to use $credential.GetNetworkCredential(). You can just use $credential.username.

  • Aditya says:

    Hi Luke,
    I tried your second and third method. I was able to encrypt the password successfully but while using it in script, I was getting access denied everytime. But when I used the user name and password explicitly, it worked without any issues. Could you please guide me what could be the possible issue?
    When I specified username and password directly, it worked

    Add-Type -Path “C:\Program Files (x86)\WinSCP\WinSCPnet.dll”
    $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Sftp
    HostName = “abc.xyz.com”
    PortNumber = 222
    UserName = “username”
    Password = “password”
    SshHostKeyFingerprint = “xxxxxx=”
    }

    But when I tried your method:

    Add-Type -Path “C:\Program Files (x86)\WinSCP\WinSCPnet.dll”
    #$password = Get-Content C:\password\password.txt | ConvertTo-SecureString -Key (Get-Content C:\password\aes.key)
    #$credential = New-Object System.Management.Automation.PsCredential(“username”,$password)

    # Set up session options
    $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Sftp
    HostName = “abc.xyz.com”
    PortNumber = 222
    UserName = “$credential.UserName”
    Password = “$credential.Password”
    SshHostKeyFingerprint = “xxxxxx=”
    }
    I get the following error:

    Exception calling “Open” with “1” argument(s): “Connection has been unexpectedly closed. Server sent command exit status 0.
    Authentication log (see session log for details):
    Using username “System.Management.Automation.PSCredential.UserName”.
    Access denied.
    Authentication failed.”

    • $credential.password is going to be encrypted since it is a “securestring”. You will want to replace that with this which will decrypt the string and input the password as clear text: ($credentials.GetNetworkCredential()).Password

  • ctmax says:

    cool and good reference..

  • Sean says:

    Hi Luke, thank you for this great article.
    im having issues using the PSCredential object with the command: send-mailmessage.

    this is what i have:
    $Key = New-Object Byte[] 32
    [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key)
    $Key | out-file C:\temp\aes.key

    (get-credential).Password | ConvertFrom-SecureString -key (get-content C:\temp\aes.key) | set-content “C:\temp\password.txt”

    $password = Get-Content C:\temp\password.txt | ConvertTo-SecureString -Key (Get-Content C:\temp\aes.key)
    $credential = New-Object System.Management.Automation.PsCredential(“user1”,$password)

    then i use the $credential object like so:
    Send-MailMessage -To “[email protected]” -From “[email protected]” -Subject “test” -Body “test” -SmtpServer “smtpserver” -Credential $credential

    but i keep getting: “The server response was: 5.7.1 Client was not authenticated”

    if i enter the creds manually using -credential (get-credential) with same creds it works…

    Thanks,
    Sean

    • Hi Sean,

      First verify that your $credential variable contains the correct password that you need by running: ($credential.GetNetworkCredential()).password this should display your password in plaintext. I would start there to make sure that your pulling the proper credentials.

    • AndrewS says:

      Hi, Sean.
      Perhaps you need to specify additional authentication parameters on the smtp server, for example, the port and the need to use the SSL(TLS) protocol.
      Send-MailMessage -To “[email protected]” -From “[email protected]” -Subject “test” -Body “test” -SmtpServer “smtpserver” -Credential $credential -Port 587 -UseSSL

  • Tony says:

    Thanks for your great post. I followed the steps correctly, but for options 2 & 3, when i issue $credential, i get only the username and not the password string like you had in option 2. Am i doing something wrong?

    Can the password be created in any directory or does it have to be only in the C:? i tried several different locations and still cannot get the password. When i try running my script, it still asks for username and password.

    Appreciate any assistance.

  • Saber Ali says:

    i changed the password of the server , the script dose not work again, what may spouse to do ?

    • Hello,

      You will need to update the credentials to reflect the new password. You could create a script that changes the password of the server and then updates the encrypted credential file.

  • Dan Apprentice says:

    Luke, really appreciated this article. I have a situation I’m wondering if I can use any of your suggestions above towards.
    I have 10 windows 10 pc’s in a workgroup (not joined to a domain). They all have set up an admin account with same username (Star1) and password.
    I want the main script to be able to read from user.csv file and use a foreach loop to create 2-3 users.
    How do I handle the password portion in the csv? Create from the actual password a SecureString, using code such as:
    $SecureStringAsPlainText = $SecurePassword | ConvertFrom-SecureString

    Then past that long encrypted string in the password section of the csv?

    If I do this, can I then create these users from that script on the usb by either: (1) going to each machine with the usb drive and running the script OR (2) PSremoting to the remote machines using this script?

    Either way, how would I do this? If there is a better more efficient manner, please share.

    I hope what I’m asking is clear and you have enough information to go on. If not, please let me know.

    Thanks in advance for whatever help you (or anyone else) can give.
    Right now, I’m stumped.

  • Shreyas Partake says:

    Hey Luke,

    I have been looking for this for a few days. Finally, I got your blog and the purpose is fulfilled. 2nd method worked for me. Thank you so much for this information!

Leave a comment

Your email address will not be published.