SecureString encryption

From the book “Powershell in depth” (p.177): “The password can only be decrypted using the private key, which exists only on the computer where the credential was created.”

Several other ressources reports that the encryption is done with the private key of the user who created the credential / encrypted string.

When running tests with stored credentials (without using the -key Parameter), I couldn’t decrypt the password neither with an other user on the same machine nor with the same domain user on another machine.

So is the encryption done with both keys - user and machine? Or did I mess up something during my tests?

SecureStrings are encrypted using the Data Protection API (DPAPI). If you really want to get into the gory details of how that works, start here:

The short answer is that, yes, the keys are specific to both the user and machine where the encryption took place, by default.

I just ran across this very issue trying to load balance a PowerShell script that syncs Exchange Calendars via EWS using impersonation. I saved the credentials for the impersonation account in an XML file and realized after testing it on separate machines that this wasn’t going to work due to the fact that the Data Protection API (DPAPI) uses both the user and machine to create the key. Does anyone have an alternate solution I can employ without hardcoding the password in the script? My fall back is to create an impersonation task account and have the script run under that user.

That depends mainly on where you want to fall on the Security <—> Usability scale. There’s really no 100% secure way to store secret data with just software, but DPAPI is about as good as it gets. With that in mind, I’d probably take the approach of encrypting the password as a SecureString on each machine that needs to run the script. You could even do it remotely with Invoke-Command to keep the manual effort down. Something like this:

$cred = Get-Credential # The account that will be running the scheduled task on the remote machines
$remoteComputers = @('Computer1','Computer2','Computer3')
$savedPassword = 'SomePasswordIWantToEncrypt'

$scriptBlock = {
    $args[0] |
    ConvertTo-SecureString -AsPlainText -Force |
    ConvertFrom-SecureString |
    Set-Content $home\Documents\EncryptedPassword.txt

# The -UseSSL switch is here to avoid sending the password over the network in plain text, but you'd have to have configured HTTPS Listeners on the remote computers.
Invoke-Command -ComputerName $remoteComputers -Credential $cred -ScriptBlock $scriptBlock -ArgumentList $savedPassword -UseSSL

If you’re using PowerShell 3.0 you can directly output a PSCredential object to disk:

Get-Credential | Export-Clixml $path

And read it back in:

$cred = Import-Clixml $path

The conversion is done automatically, which is a nice feature. Be sure to restrict your script to PS 3.0 since the password will not be saved to file with earlier versions.

Read more about PowerShell version issues here: