powershellsecurestring

Powershell 5. SecureString decryption that minimizes time in memory


I have a script that uses openssl.exe to view a private key file. This question, however, relates to any call to an external program.

The password is requested through read-host -asSecureString.

The secureString type can't be passed to the openssl.exe command. I need to decrypt it to a plain String, then pass that String to openssl.exe

Is it possible to minimize the amount of time the string is plain-text in memory? Note that we're on Powershell 5. PS 7 features aren't available to us.

Two methods I see:

#Decrypt the secureString to a variable. 
#Create and remove that variable as soon as possible.
$securePass = Read-Host("Please enter a password") -asSecureString
$plainPass = [Net.NetworkCredential]::new('',$securePass).password
#Note there are many ways to perform the above step. If the 'Marshal method' is better, let me know.
./openssl.exe rsa -in user.key -passin pass:$plainpass
Remove-Variable $plainPass
#Decrypt the SecureString in the call to openssl. 
#Hope Powershell's garbage collection removes the String object as soon as the command is issued. 
$securePass = Read-Host("Please enter a password") -asSecureString
./openssl.exe rsa -in user.key -passin pass:$([Net.NetworkCredential]::new('',$securePass).password)
  1. Is one of these methods better than the other?
  2. Given that the script is very quick, and the String is never written to file, is using secureString even an issue?
  3. Do I break security best practice by using a String, regardless of how little time it exists?

Solution

  • Since you are prompting anyway, why not just use Get-Credential vs Read-Host?

    $Creds = Get-Credential -Credential "$env:USERDOMAIN\$env:USERNAME"
    $Creds.UserName
    $Creds.Password
    $Creds.GetNetworkCredential().Password
    # Results
    <#
    lab01\postanote
    System.Security.SecureString
    !SomeSuperSecretPassword1
    #>
    

    Practices as per Microsoft: Working with Passwords, Secure Strings, and Credentials in Windows PowerShell

    You should always clean up behind yourself. Don't wait for the system to do it.

    Here is a suggested way I've given to others in classes/workshops/engagements I deliver.

    # Collect all automatic and default loaded resources, put this at the top of your script
    $AutomaticVariables                = Get-Variable
    $AutomaticAndProfileLoadedVModules = Get-Module
    $AutomaticLoadedFunctions          = Get-Command -CommandType Function
    
    # Run this as the last items in your script
    Function Clear-ResourceEnvironment
    {
        [CmdletBinding(SupportsShouldProcess)]
    
        [Alias('cre')]
    
        Param
        (
            [switch]$AdminCredStore
        )
    
        # Clear instantiate reasource interop
        $null = [System.Runtime.InteropServices.Marshal]::
                ReleaseComObject([System.__ComObject]$Shell)
    
        # instantiate .Net garbage collection
        [System.GC]::Collect()
        [System.GC]::WaitForPendingFinalizers()
    
        # Clear all PSSessions
        Get-PSSession | 
        Remove-PSSession -ErrorAction SilentlyContinue
    
        # Clear static credential store, if switch is used
        If ($AdminCredStore)
        {Remove-Item -Path "$env:USERPROFILE\Documents\AdminCredSet.xml" -Force}
        Else
        {
            Write-Warning -Message "
            `n`t`tYou decided not to delete the custom Admin credential store.
            This store is only valid for this host and and user $env:USERNAME"
        }
    
        Write-Warning -Message "
        `n`t`tRemoving the displayed session specific variable and module objects"
    
        # Clear only variables created / used during the session
        Compare-Object -ReferenceObject (Get-Variable) -DifferenceObject $AutomaticVariables -Property Name -PassThru |
        Where -Property Name -ne 'AutomaticVariables' |
        Remove-Variable -Verbose -Force -Scope 'global' -ErrorAction SilentlyContinue
        Remove-Variable -Name AdminCredStore -Verbose -Force
    
        # Clear only modules loaded during the session
        Compare-Object -ReferenceObject (Get-Module) -DifferenceObject $AutomaticAndProfileLoadedVModules -Property Name -PassThru |
        Where -Property Name -ne 'AutomaticAndProfileLoadedVModules' |
        Remove-Module -Force -ErrorAction SilentlyContinue
    
        # Clear only functions loaded during the session
        Compare-Object -ReferenceObject (Get-Command -CommandType Function) -DifferenceObject $AutomaticLoadedFunctions  -Property Name -PassThru |
        Where -Property Name -ne 'AutomaticLoadedFunctions' |
        Remove-Item -Force -ErrorAction SilentlyContinue
    }
    

    Point of note:

    The moment you use decrypt to plain text it's an issue if one can sniff what you are doing. So, the adage, Secure the channel, Secure the data, or both apply.

    Also, if any real logging (SEIM tools, PowerShell Auditing, Transcript) is in place, no matter what you do to clear it from RAM, it's in the logs for all to see, unless you remember to clear that entry.