Saving Passwords (and preventing other processes from decrypting them)

This question is nothing new: "How do I save credentials in PowerShell so I don't have to enter a password every time the script runs?" An answer to that question has been in PowerShell for a very long time: you use the ConvertFrom-SecureString cmdlet to encrypt your password, save the resulting encrypted string to disk, and then later reverse the process with ConvertTo-SecureString. (Alternatively, you can use Export-CliXml, which encrypts the SecureString the same way.) For example:

# Prompt the user to enter a password
$secureString = Read-Host -AsSecureString "Enter a secret password"

$secureString | ConvertFrom-SecureString | Out-File -Path .\storedPassword.txt

# Later, read the password back in.
$secureString = Get-Content -Path .\storedPassword.txt | ConvertTo-SecureString

The ConvertFrom-SecureString and ConvertTo-SecureString cmdlets, when you don't use their -Key, -SecureKey, or -AsPlainText switches, use DPAPI to encrypt / decrypt your secret data. When it comes to storing secrets with software alone (and without requiring a user to enter a password), DPAPI's security is about as good as it gets. It's not unbreakable - all of the encryption keys are there to be compromised by someone with Administrator access to the computer - but it's pretty good.

You can see the details on how DPAPI works in the linked article, but here's the long and short of it: By default, only the same user account (and on the same computer) is able to decrypt the protected data. However, there's a catch: any process running under that user account can freely decrypt the data. DPAPI addresses this by allowing you to send it some optional, secondary entropy information to be used in the encryption and decryption process. This is like a second key that is specific to your program or script; so long as other processes don't know what that entropy value is, they can't read your data. (In theory, now you have a problem with protecting your entropy value, but this at least adds an extra layer that a malicious program needs to get around.) Here's an excerpt from the article:

A small drawback to using the logon password is that all applications running under the same user can access any protected data that they know about. Of course, because applications must store their own protected data, gaining access to the data could be somewhat difficult for other applications, but certainly not impossible. To counteract this, DPAPI allows an application to use an additional secret when protecting data. This additional secret is then required to unprotect the data.

Technically, this "secret" should be called secondary entropy. It is secondary because, while it doesn't strengthen the key used to encrypt the data, it does increase the difficulty of one application, running under the same user, to compromise another application's encryption key. Applications should be careful about how they use and store this entropy. If it is simply saved to a file unprotected, then adversaries could access the entropy and use it to unprotect an application's data.

Unfortunately, the ConvertFrom-SecureString and ConvertTo-SecureString cmdlets don't allow you to specify this secondary entropy; they always pass in a Null value. For that reason, I'm putting together a small PowerShell script module to add this functionality. It can be downloaded from the TechNet repository. It adds an optional -Entropy parameter to both ConvertFrom-SecureString and ConvertTo-SecureString.

Since I was tinkering with those commands anyway, I also added an -AsPlainText switch to the ConvertFrom-SecureString command, in case you want to get the plain text back. This saves you the couple of extra commands to set up a PSCredential object and call the GetNetworkCredential() method, or make the necessary calls to the Marshal class yourself.

Import-Module .\SecureStringFunctions.psm1
$secureString = Read-Host -AsSecureString "Enter a secret password." 

# You can pass basically anything as the Entropy value, but I'd recommend sticking to simple value types (including strings),
# or arrays of those types, to make sure that the binary serialization of your entropy object doesn't change between
# script executions.  Here, we'll use Pi.  Edit:  The latest version of the code enforces the use of the recommended simple
# types, unless you also use the -Force switch.

$secureString | ConvertFrom-SecureString -Entropy ([Math]::PI) | Out-File .\storedPassword.txt

# Simulating another program trying to read your value using the normal ConvertTo-SecureString cmdlet (with null entropy).  This will produce an error. 
$newSecureString = Get-Content -Path .\storedPassword.txt | ConvertTo-SecureString 
 
# When your program wants to read the value, it can do so by passing the same Entropy value. 
$newSecureString = Get-Content -Path .\storedPassword.txt | ConvertTo-SecureString -Entropy ([Math]::PI)

#Display the plain text of the new SecureString object, verifying that it was decrypted correctly.
$newSecureString | ConvertFrom-SecureString -AsPlainText -Force
About the Author

Dave Wyatt

Dave Wyatt is a Microsoft MVP (PowerShell) and a member of PowerShell.org's Board of Directors.

3 Comments

  1. If this is available only from the same account, then how do you use this if you want to write a script to say authenticate to remote file server and run as scheduled task under network service account

    • Hi GS,

      If you'll be scheduling your script to run as the Network Service account, then you'll also need to create the encrypted password file using that same account (if you're using the ConvertFrom / ConvertTo-SecureString cmdlets and DPAPI.) Network Service has a user profile of its own in C:\Windows\ServiceProfiles\NetworkService , and I assume it has its own DPAPI master keys as well, though I haven't tested this functionality for something running as Network Service myself.

      In this case, you can probably also avoid the need to store credentials at all. When something runs as Network Service and needs to contact a remote computer, it authenticates using your computer's Active Directory account, which can be added to Security groups or granted permissions the same as any User account. You could also just schedule the task to run as a user who has permissions to the remote file server; either way, the script can authenticate as the user who ran it, rather than having to specify alternate credentials.