Skip to the main content.

Unlocking the Power of PowerShell: Tips for Success

Enhancing SecureString security: Best practices on different platforms

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 $path 

 

It 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().Password 

 

Anyone 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 $path 

 

The 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)
$plaintext 

 

As 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):

 OverloadDefinitions  

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.SS 

 

The result looks similar to this:

01000000d08c9ddf0115d1118c7a00c04fc297eb01000000fca013869c24324fa191f7d1f5e8d46a00000000020000000000106600000001000020000000b49c91059f0c802dee4055cbbed6901038bf3c5436e94cce9385e7f643f86467000000000e800000000200002000000090a8c203312e7dd2b8c7273b267d27ba50ca8e1216e2b1c236037a2ba004978e1000000060298f08ff28e517b415dc35485a9faf400000005c64a32f404da2ff23b19c5771edd0dd38f95fbe2200c5e254d8c4c31fb166f1151f852f1018b2174760988fde826de6787425e82f01415ed5011cb2f5a08b47

 

Let’s 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.

 

In case you missed the first deep dive on SecureString in .NET, which answered the question how secure your sensitive data was across platforms, read the full text: Cross-platform encryption risks with .NET's SecureString

 

Good2know

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.

PowerShell Poster 2023

Get your poster here!

 

 

Related links 

 

Related posts

6 min read

Encoding vs encryption? Understand the difference and choose wisely!

Understanding encoding vs encryption is essential for robust data security. But what is the difference between encoding...

6 min read

Enhancing SecureString security: Best practices on different platforms

How does Windows keep SecureString safe? Dive deeper into encryption methods and uncover cross-platform security gaps...

6 min read

Cross-platform encryption risks with .NET's SecureString

Think your SecureString is safe? Not on all platforms! Discover the risks of cross-platform data encryption with ...

About the author: