PowerShell and the secure strings
2024-02-18How we can work with secure strings and is it possible to compare secure strings or made it readable again?
Cause of this I have written this article, and I hope to answer sone questions about secure strings.
Are secure strings secure?
The answer to this depends on the point of view.
The main fact for a secure string is, that after creating a secure string, it is not useable for other users per default, making working with secure strings relatively secure.
But methods are available to return the cleartext string from the secure string. This is not as bad as it sounds.
Comparing secure strings
If we consider entering a password twice to ensure that you have no typos in it, we have to compare the passwords. If we try to compare the two secure strings directly this will not work properly :
$sec1 = Read-Host -AsSecureString -Prompt "Enter new password"
$sec2 = Read-Host -AsSecureString -Prompt "Re-Enter new password"
$sec1 -eq $sec2
False
and this becomes clear if we use the ConvertFrom-SecureString
CmdLet:
Both variables contain the secure string for "Test1" but if we use the ConvertFrom-SecureString
CmdLet we can see that we get back 2 fully different strings.
ConvertFrom-SecureString $sec2
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000027154735e0b6d4458f51d5b42e5dc05700000000020000000000106600000001000020000000536db4dd82716b3a2e20bf608be08bcb90cbfb0be813eb0cdc167e7a15e32593000000000e800000000200002000000032cba0cc1a075c3761da819c89354c6bdcacc4476c927b27bceb87aee8000d73100000009b4b013c9e09c9379b92f134602bc32540000000d98b339cf214f40b2efcaebff2f9ccb9c339c49cba2f321238b48a4a81f930493ba39563712a14911a786f49e360c1e250426ad3efd1bdfc8a30b63edd3c010d
ConvertFrom-SecureString $sec1
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000027154735e0b6d4458f51d5b42e5dc05700000000020000000000106600000001000020000000d34b04ac1456b2f4a71ab380c150fa6262e7f9dbe89c49477984af1724b489e9000000000e8000000002000020000000a10b035236fc20dca29710909ef79c27b558be621c83ead2ef30d88b469ad21910000000d56dbd8e982a83eaf98f1c83d89dd0f4400000007821a7c7178a20819da44706e2d65a9e4982b96a5b7e89111f003618116d756acf8d34234d4da88a1d24e4f0ad929d0e40b90877b5ed71aa28aef8e54efd7249
As we can see, we have to find another method to compare the secure string.
Converting secure string back to clear text
To compare the two secure strings, we have to convert them back to plain text.
This is much easier than you think.
There are two main methods to do this. The first one uses the GetNetworkCredential()
method of the System.Management.Automation.PSCredential
Class.
The GetNetworkCredential()
method
$PlainPassword="TestPwd"
# Converting plain text password to a secure string
$SecurePassword = ConvertTo-SecureString $PlainPassword -AsPlainText -Force
<#
Create a pscredential object and calling method GetNetworkCredential().
Then use the Password property of the System.Net.NetworkCredential object
#>
$UnsecurePassword = (New-Object pscredential 0,$SecurePassword).GetNetworkCredential().Password
#Output the plain text password
$UnsecurePassword
or
$PlainPassword="TestPwd"
# Converting plain text password to a secure string
$SecurePassword = ConvertTo-SecureString $PlainPassword -AsPlainText -Force
#Create a pscredential object
$credential=New-Object pscredential 0,$SecurePassword
#calling method GetNetworkCredential().Then use the Password property of the System.Net.NetworkCredential object
$UnsecurePassword = $credential.GetNetworkCredential().Password
#Output the plain text password
$UnsecurePassword
The universal way
$PlainPassword="TestPwd"
# Converting plain text password to a secure string
$SecurePassword = ConvertTo-SecureString $PlainPassword -AsPlainText -Force
# Copies the content of secure string into an unmanaged binary string and returns an integer pointer (System.IntPtr)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
#Create a managed string and copy the binary string from unmanaged memory into it
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
# overwrite the allocated unmanaged memory with zeros then free the memory of the binary string
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
#Output the plain text password
$UnsecurePassword
I for my part, prefer this way because with ZeroFreeBSTR
I destroy the first critical object explicitly.
After doing what you want with the plain text password, you should immediately remove the variable using Remove-Variable
Example
function Convert-SecureStringToPlainText{
param(
[Parameter(Mandatory=$true)]
[securestring]$SecureString
)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
# Create a managed string and copy the binary string from unmanaged memory into it
$PlainTextString = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
# overwrite the allocated unmanaged memory with zeros then free the memory of the binary string
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
#Output the plain text password
return $PlainTextString
}
$PasswordsEqual=$false
do {
$NewPwd=Read-Host -AsSecureString -Prompt "Enter new password"
$ReEnteredNewPwd=Read-Host -AsSecureString -Prompt "Re-Enter new password"
if((Convert-SecureStringToPlainText -SecureString $NewPwd) -eq (Convert-SecureStringToPlainText -SecureString $ReEnteredNewPwd)) {
$PasswordsEqual=$true
Remove-Variable -Name ReEnteredNewPwd -Force -Confirm:$false
} else {
Write-Host "Your passwords are not equal. Please try again" -ForegroundColor Yellow
}
} until ($PasswordsEqual)
Write-Host "Your new password has been set" -ForegroundColor Yellow