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...
Unlocking the Power of PowerShell: Tips for Success
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.
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.
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.
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.
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.
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
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.
Nov 27, 2024 by Aleksandar Nikolić and Dr. Tobias Weltner
Understanding encoding vs encryption is essential for robust data security. But what is the difference between encoding...
Nov 20, 2024 by Aleksandar Nikolić and Dr. Tobias Weltner
How does Windows keep SecureString safe? Dive deeper into encryption methods and uncover cross-platform security gaps...
Nov 18, 2024 by Aleksandar Nikolić and Dr. Tobias Weltner
Think your SecureString is safe? Not on all platforms! Discover the risks of cross-platform data encryption with ...
Tobias Weltner and Aleksandar Nikolić joinly wrote the blog post series 'Tobias&Aleksandar's PowerShell tips'. So we introduce both of them here:
----------------------------
Aleksandar Nikolić is a Microsoft Azure MVP and co-founder of PowerShellMagazine.com, the ultimate online source for PowerShell enthusiasts. With over 18 years of experience in system administration, he is a respected trainer and speaker who travels the globe to share his knowledge and skills on Azure, Entra, and PowerShell. He has spoken at IT events such as Microsoft Ignite, ESPC, NIC, CloudBrew, NTK, and PowerShell Conference Europe.
----------------------------
Tobias is a long-time Microsoft MVP and has been involved with the development of PowerShell since its early days. He invented the PowerShell IDE "ISESteroids", has written numerous books on PowerShell for Microsoft Press and O'Reilly, founded the PowerShell Conference EU (psconf.eu), and is currently contributing to the advancement of PowerShell as member in the "Microsoft Cmdlet Working Group". Tobias shares his expertise as a consultant in projects and as a trainer in in-house trainings for numerous companies and agencies across Europe.