How does Windows keep SecureString safe? Dive deeper into encryption methods and uncover cross-platform security gaps in the second part of our detailed guide.
In the first part of this mini-series, we explored SecureStrings, how they are automatically encrypted on Windows, and how they remain unencrypted on Linux and macOS.
Quick recap: How automatic encryption works
Today, we will examine the underlying encryption API to understand how the Windows platform differs from others and why SecureStrings are securely AES-encrypted on Windows but only encoded on Linux and macOS.
In part one, we reviewed various code examples, including this one:
$path = "$env:userprofile\mypasswords.xml"
@{
Server1 = Get-Credential -Message 'Enter password for Server 1' -UserName $env:USERNAME
Server2 = Get-Credential -Message 'Enter password for Server 2' -UserName $env:USERNAME
Server3 = Get-Credential -Message 'Enter password for Server 3' -UserName $env:USERNAME
} | Export-Clixml -Path $pathIt asks for three credentials and then saves them to an XML file. Inside this file, all SecureStrings are auto-magically encrypted:
<ToString>System.Management.Automation.PSCredential</ToString><Props> <S N="UserName">tobia</S> <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb01000000fca013869c24324fa191f7d1f5e8d46a00000000020000000000106600000001000020000000de26f1058a814b27a481821459eea01f8ad340a87512a6be0c21f871b46e8ee6000000000e8000000002000020000000cc97abd02f4f296d16338711331f1e9893e36da5ba397a3286891b06d329ff6710000000c7f9f9af2a5d3e7b980cab7acc7b22ee400000006dfc7c22382502e9ec47eb0ea28f990807dc336f1d673a06754c9de5a794b077633217176cfa027ee180073bb42965128442c917f40a25d7150d5536e325f82f</SS></Props>
You (and only you, and only on the machine where you encrypted the secrets) can easily decrypt the secrets. Thus, you and your machine are the transparent secrets used to protect the sensitive information:
$path = "$env:userprofile\mypasswords.xml"
$mySecrets = Import-Clixml -Path $path
# Retrieve the stored credentials
$mySecrets.Server1
# I am the owner, so I can always see the plain text, too
$mySecrets.Server1.GetNetworkCredential().Password
$mySecrets.Server2
$mySecrets.Server2.GetNetworkCredential().Password
$mySecrets.Server3
$mySecrets.Server3.GetNetworkCredential().PasswordAnyone else who gets their hands on the XML file does not have access to the secrets.
API: Protect() and Unprotect()
Behind the scenes, on Windows only, .NET uses an API interface to perform encryption and decryption, which you can also access directly. Let's mimic directly what happened in the examples above.
This script prompts for a plaintext secret and safely encrypts it to disk. It uses the Protect() method and the data protection scope "CurrentUser": the AES key is automatically generated based on your identity and PC.
$path = "$env:temp\secret1.txt"
$secretMessage = Read-Host -Prompt 'Enter a plain text secret'
Add-Type -AssemblyName System.Security
# convert text to bytes in UTF8 encoding
$bytes = [System.Text.Encoding]::UTF8.GetBytes($secretMessage)
# encrypt bytes using the built-in secret
$bytesEncrypted = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
# convert bytes to Base64 encoding
$string = [Convert]::ToBase64String($bytesEncrypted)
# write content to file
Set-Content -Path $path -Value $string -Encoding UTF8
# view file content
notepad $pathThe encrypted secret is stored in Base64 format and looks similar to this:
AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAA/KAThpwkMk+hkffR9ejUagAAAAACAAAAAAAQZgAAAAEAACAAAACP381DtngZmmO7VaQSkRvSjBzQVXOxeGHYj01yQsuuxQAAAAAOgAAAAAIAACAAAADqEoinaw5VNx1F9AMdJCI67N+SbgzdrR+iK9LBgMqXXhAAAABMtA1+Stck7j4XmrgYMGpLQAAAAGsK+mzvebNhNrRWtnO/ijhmyYffw7byoGI/0pFSn45+BC7oz05XJa853Kw8PRsVouGb+umlbGVjH/6kXD0GKFA=
To decrypt the secret without the need for supplying a secret, use this:
$path = "$env:temp\secret1.txt"
Add-Type -AssemblyName System.Security
# read Base64 encoded text
$base64 = Get-Content -Path $path -Encoding UTF8
# convert to encrypted bytes
$bytesEncrypted = [Convert]::FromBase64String($base64)
# decrypt using built-in secret
$bytes = [System.Security.Cryptography.ProtectedData]::UnProtect($bytesEncrypted, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
# convert bytes to string
$plaintext = [System.Text.Encoding]::UTF8.GetString($bytes)
$plaintextAs with SecureString serialization, only the person who encrypted the secret can decrypt it (on the PC where it was encrypted). Anyone else who gains access to the file cannot decrypt it.
Other protection options
Using Protect() and Unprotect() directly provides additional options not available with the default SecureString encryption. For example, you can change the DataProtectionScope from CurrentUser to LocalMachine. This means that only the PC is used as the secret, allowing all users on the machine to decrypt it, thereby loosening security.
Similarly, you can also strengthen security. The Protect() and Unprotect() methods allow for an additional "salt" (a third secret):
The "salt" is the optionalEntropy parameter, which is a byte array. When you protect a secret with optional entropy, you need to include this "salt" (byte array or additional password) to decrypt the secret.
Proof: SecureString uses Protect() API
Let's prove that transparent SecureString encryption relies on the underlying Protect()/Unprotect() API (on Windows, but not on Linux or other platforms):
# use transparent SecureString encryption
$path = Join-Path -Path $env:temp -ChildPath testsecret.xml
Read-Host -Prompt 'Enter password' -AsSecureString | Export-Clixml $path
# open XML file
$xmlFile = [xml]::new()
$xmlFile.Load($path)
# output encrypted SecureString from file
$xmlFile.objs.SSThe result looks similar to this:
01000000d08c9ddf0115d1118c7a00c04fc297eb01000000fca013869c24324fa191f7d1f5e8d46a00000000020000000000106600000001000020000000b49c91059f0c802dee4055cbbed6901038bf3c5436e94cce9385e7f643f86467000000000e800000000200002000000090a8c203312e7dd2b8c7273b267d27ba50ca8e1216e2b1c236037a2ba004978e1000000060298f08ff28e517b415dc35485a9faf400000005c64a32f404da2ff23b19c5771edd0dd38f95fbe2200c5e254d8c4c31fb166f1151f852f1018b2174760988fde826de6787425e82f01415ed5011cb2f5a08b47
Lets try and decipher the encrypted hex string that was produced by the automatic SecureString conversion by using the Unprotect() API function directly:
# our secret information
$secureString = Read-Host -Prompt 'Enter password' -AsSecureString
# text -> Encryption (using Export-CliXml)
$path = Join-Path -Path $env:temp -ChildPath testsecret.xml
$secureString| Export-Clixml $path
# retrieve encrypted text from file
$xmlFile = [xml]::new()
$xmlFile.Load($path)
$stringFromSecureString = $xmlFile.objs.SS
# encryption -> Text (using DPAPI/Unprotect())
# convert hex string to byte array
$bytesFromSecureString = for ($i = 0; $i -lt $stringFromSecureString.Length; $i += 2) {
[Convert]::ToByte($stringFromSecureString.Substring($i, 2), 16)
}
# unprotect (decrypt) information
$bytesPlain = [System.Security.Cryptography.ProtectedData]::UnProtect($bytesFromSecureString, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
# convert bytes to string (Unicode-Encoding!)
$plaintext = [System.Text.Encoding]::Unicode.GetString($bytesPlain)
$plaintext The test is successful: you enter plaintext, it is automatically encrypted and written to XML, and the API's Unprotect() method can take the encrypted hex string and convert it back to plaintext. Just note that automatic SecureString encryption uses Unicode as the text encoding, whereas UTF8 was used in the initial examples.
Summary
In a nutshell, you now understand how SecureStrings are securely persisted on Windows, and you can mix and use transparent SecureString encryption with direct calls to DPAPI methods.
SecureStrings This was our deep dive
- In our first article, cross-platform encryption risks with .NET's SecureString, we looked at how secure your sensitive data was, not only on Windows, but across platforms.
- To help you enhance SecureString security, we then presented the best practices on different platforms.
- In the third article, we recommended: Encoding vs encryption? Understand the difference and choose wisely!
- And last but not least, we hope to have helped you with mastering AES encryption.
Your ultimate PowerShell Cheat Sheet
Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.The poster is available for download and in paper form.
Related links
- ScriptRunner ActionPacks will help you automate tasks
- Try out ScriptRunner here
- ScriptRunner: Book a demo with our product experts


