Skip to main content

Getting Started With PSEncryptedData

When I was trying to figure out how my team or other dev teams could start using DSC I had a couple goals I wanted to try to achieve:

  1. Highly privileged credentials will be required and must be secured
  2. Source control will be used to track changes and to automate builds
  3. Provide both the admins and dev teams a way to bring their configurations together

These goals gave rise to a number of possible options, from simple to complex. If you have used DSC beyond a sample configuration then you know that eventually you need to use PSCredential in your configuration and that you can specify in your configuration how you want those PSCredential values encrypted at rest in the MOF file.

But how do you get those PSCredential objects into your configuration file if there is no human to provide input to Get-Credential? Sure you can use ConvertTo-SecureString -String 'secret' -AsPlainText -Force and if you can guarantee where you store the configuration script is secure then that may be fine.

You could also on your build system login/runas the account that will perform the build and use ConvertFrom-SecureString to produce an encrypted secret that only that user on that system will be able to use. How do you share that login credential with others so that they can produce their own encrypted secret?

You can even used a shared key with ConvertFrom-SecureString but then how do you secure access to that key?

You could even create a central application that encrypts a secret for a user using a shared secret that is only accessible on the central application and DSC build system either via a secured file/registry key. But what happens when you need to rotate that shared secret?

Certificate Encryption

If you are using DSC, you are most likely using certificates to encrypt the PSCredential values in the MOF configuration file as a best practice, because passwords in the clear are a really bad idea and other methods mentioned above can be tricky.

Every certificate has two pieces of information, a public key and a private key. Just as the names state the private key is meant to be kept private and the public one can be used by anyone.

You don't typically encrypt data with a private key because anyone could decrypt it with the public key. This method is typically used for signing data that comes from the private entitiy but that's another blog entry and I digress.

However, if we encrypt a secret with the public key, only the entities with the private key will be able to decrypt the secret.

So, if anyone can encrypt data using the public key which can be distributed, only the entity with the private key, e.g. DSC build server(s) will be able to decrypt the secret before stuffing it into a PSCredential or SecureString.


If you want to get started and don't have the module installed, you can get it from the PowerShell Gallery which is by far my favorite feature of PowerShell now.

Install-Module PSEncrytpedData

Once you have it installed you'll need a certificate. PowerShell v5 has an enhanced New-SelfSignedCertificate cmdlet that you can use to generate the appropriate certificate as show here:

New-SelfSignedCertificate -Subject 'CN=PowerShell DSC Encryption'

However, if you are stuck in PowerShell v4 like I was when I first made the module, you can use the New-PSEncryptedDataCertificate function as shown here:

New-PSEncryptedDataCertificate -Name 'PowerShell DSC Encryption'

Great, so you have a certificate, you should generate it on the system where you want the private key to exist, then export the public key with Export-Certificate and you should probably also use Export-PfxCertificate to export the entire certificate for source control. This is ok, there is a shared password you will need to access the certificate pfx file to be able to reinstall or install it on other systems.

In the end, you should be able to see a certificate installed into some location in your key store:

PS C:\> Get-ChildItem Cert:\LocalMachine\My

   PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My

Thumbprint                                Subject
----------                                -------
B6052E85F1EB02011AC34B05042EC085080B29F6  CN=PowerShell DSC Encryption

Data Encryption

The core of the module is all about encrypting data at rest. It takes string or SecureString values and encrypts them with the specified certificate and stores what information is needed to decrypt the data.

You can specify the certificate in three different ways.

  1. Path to the certificate, e.g. Cert:\LocalMachine\My\THUMBPRINT
  2. The thumbprint of the certificate
  3. The X509Certificate2 instance

You can now encrypt data:

'Secret' | ConvertTo-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6

This produces an encrypted base64 string:


You can also encrypt a SecureString the same way:

Read-Host -AsSecureString -Prompt 'Secret' | ConvertTo-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6

Decryption works the same way with ConvertFrom-PSEncryptedData and as long as you have access to the private key of the certificate thumbprint that was used to encrypt the string it is almost like magic.

'Secret' | ConvertTo-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6 | ConvertFrom-PSEncryptedData

If you don't have access to the private key you will see the following error:

ConvertFrom-PSEncryptedData : The certificate 'CN=PowerShell DSC Encryption' with thumbprint 'B6052E85F1EB02011AC34B05042EC085080B29F6' does not contain a private key or access is denied.

Data At Rest

Having those large base64 strings all over the DSC configuration makes it a hassle to deal with, but if you are used to things like that feel free to stop here, you know all you need to get started.

For me, I like to encapsulate complexity as much as possible and putting the encrypted data in the configuration only creates noise. To store the data instead in external files use the functions Export-PSEncryptedData and Import-PSEncryptedData.

Each are called just like ConvertTo-PSEncryptedData and ConvertFrom-PSEncryptedData except you now have to pass a file path:

'Secret' | Export-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6 -OutputPath .\secret.encrypted

Import-PSEncryptedData -Path .\secret.encrypted

PSCredential Encryption

The above could be used to encrypt the passwords of PSCredential but then you'd still have to mash that up to the username and do some calls to New-Object to get it all together.

Why do that more than once? So I wrapped it into a simple call just like export/import but instead of taking a string or SecureString it works with instances of PSCredential only.

Get-Credential | Export-PSEncryptedCredential -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6 -OutputPath .


Popular posts from this blog

PowerShell SupportsShouldProcess Worst & Best Practices

This has been a very big discussion within the Scripting Games 2013 community and I want to add my two cents in an official blog post.

I've left several people comments on how they might be misunderstanding how SupportsShouldProcess works, but I also realize, everyone of these individuals has given me more insight into its use and perhaps, how it should best be utilized.

For those of you that don't know, SupportsShouldProcess is a parameter on the CmdletBinding attribute you can place on your cmdlets that automatically adds the -WhatIf and -Confirm parameters. These will naturally flow into other cmdlets you use that also SupportsShouldProcess, e.g. New-Item, Move-Item.

The major discussion has been around, should you just let the other cmdlets handle the $PSCmdlet.ShouldProcess feature, and if not how should you implement it. ShouldProcess has the following definitions.

Generate Random SecureString Key

Ever need to encrypt a SecureString that can be used across multiple servers? I suggest storing this BASE64 value in a secure location only accessible by the account(s) that need to decrypt the SecureString.
$secret = 'secret1234'$key    = [Convert]::ToBase64String((1..32 |% { [byte](Get-Random -Minimum 0 -Maximum 255) }))  $encryptedSecret = ConvertTo-SecureString -AsPlainText -Force -String $secret | ConvertFrom-SecureString -Key ([Convert]::FromBase64String($key))  $encryptedSecret | Select-Object @{Name='Key';Expression={$key}},@{Name='EncryptedSecret';Expression={$encryptedSecret}} | fl  $ss = ConvertTo-SecureString -Key ([Convert]::FromBase64String($key)) -String $encryptedSecret(New-Object System.Management.Automation.PSCredential 'SECURESTRING',$ss).GetNetworkCredential().Password

PowerShell Error Handling Behavior Debunked

Note: I am using simple error messages as an example, please reference the best practices and guidelines I outlined on when to use custom error messages.

I have been churning in my mind for the last few days all the entries in the 2013 Scripting Games and how they handle errors, or lack thereof.

I am coming to the conclusion through some testing that the simple fact of seeing a try..catch or throw statements does not mean there is proper error handling.

I've been testing several variations and forms of error handling, so lets start with the basics.
function Test-WriteError {      [CmdletBinding()] param()  "Test-WriteError::ErrorActionPreference = $ErrorActionPreference"Move-Item -Path 'C:\Does\Not\Exists.log' -Destination 'C:\No\Where'"Test-WriteError::End"}   Test-WriteError::ErrorActionPreference = Continue
Move-Item : Cannot find path 'C:\Does\Not\Exists.log' because it does not exist.
At line:6 char:5
+     Move-Item -Path 'C:\Does\N…