Functions/New-EncryptedString.ps1
#Requires -Version 3 ## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx #region Function New-EncryptedString Function New-EncryptedString { <# .SYNOPSIS Encrypts the specified value, creates a randomized encryption key, and optionally, outputs the information the pipeline for further processing, and/or saves the information to a xml. .DESCRIPTION Provides a secure method of using sensitive data within scripts as long as best practices are followed. Keeping decryption keys separate from encrypted data is ideal. .PARAMETER Value Specifies that the value that will be encrypted. .PARAMETER EncryptionKey Specifies the encryption key used to encrypt the string provided within the value parameter. This encryption key WILL BE required for furture decryption! The value must be exactly 16, 24, or 32 characters in length. .PARAMETER Export Specifies that the generated encryption data will be exported. .PARAMETER ExportPath Full path to the file where the encryption data will be saved to. .PARAMETER Force Specifies that the generated encryption data file will be overwritten. .PARAMETER Unsecure Specifies that the encryption key will be included in the export. This would be fine for testing purposes, but generally you want to separate the encryption key from the encrypted data. .PARAMETER ContinueOnError Causes any encountered errors to be considered non-terminating. .EXAMPLE $NewEncryptedStringInfo = New-EncryptedString -Value 'YourStringValue' -Export -Force -Verbose Write-Output -InputObject ($NewEncryptedStringInfo | Format-List) .EXAMPLE $Null = Add-Type -AssemblyName 'System.Web' [String]$NewEncryptedString_Value = 'YourStringValue' [String]$NewEncryptedString_EncryptionKey = [System.Web.Security.Membership]::GeneratePassword(32, 16) [Switch]$NewEncryptedString_Export = $True [System.IO.Fileinfo]$NewEncryptedString_ExportPath = "$($Env:Temp.TrimEnd('\'))\New-EncryptedString\EncryptionInfo.xml" [Switch]$NewEncryptedString_Force = $True [Switch]$NewEncryptedString_Unsecure = $True [Switch]$NewEncryptedString_Verbose = $True [Hashtable]$NewEncryptedStringParameters = @{} $NewEncryptedStringParameters.Add('Value', ($NewEncryptedString_Value)) $NewEncryptedStringParameters.Add('EncryptionKey', ($NewEncryptedString_EncryptionKey)) $NewEncryptedStringParameters.Add('Export', ($NewEncryptedString_Export)) $NewEncryptedStringParameters.Add('ExportPath', ($NewEncryptedString_ExportPath)) $NewEncryptedStringParameters.Add('Force', ($NewEncryptedString_Force)) $NewEncryptedStringParameters.Add('Unsecure', ($NewEncryptedString_Unsecure)) $NewEncryptedStringParameters.Add('Verbose', ($NewEncryptedString_Verbose)) $NewEncryptedStringInfo = New-EncryptedString @NewEncryptedStringParameters Write-Output -InputObject ($NewEncryptedStringInfo) .EXAMPLE Loop through the values in a hashtable and encrypt each value using a randomly generated encryption key. Afterwards, separately export the encrypted data, and decryption key to their respective JSON formatted files. The encrypted values can be transported along with the script and the respective decryption key can be retrieved from a remote location. An alternative is that both the encrypted data and decryption key can be stored in a remote location. This will be useful for ensuring that sensitive data only runs through memory on the endpoint during process execution. Afterwards, the sensitive data is no longer exposed. [HashTable]$ValuesToEncrypt = [Ordered]@{} $ValuesToEncrypt.Add('DomainFQDN', 'mydomain.net') $ValuesToEncrypt.Add('DomainNBN', 'MyDomain') $ValuesToEncrypt.Add('UserName', 'MyReadOnlyUser') $ValuesToEncrypt.Add('Password', 'MyReadOnlyUserPassword') [System.Collections.ArrayList]$OutputObject = @() [System.IO.DirectoryInfo]$EncryptionInfoDirectory = "$($Env:Temp.TrimEnd('\'))\New-EncryptedString" [System.IO.DirectoryInfo]$EncryptedDataDirectory = "$($EncryptionInfoDirectory.FullName)\EncryptedData" If ($EncryptedDataDirectory.Exists -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($EncryptedDataDirectory.FullName)} [System.IO.DirectoryInfo]$DecryptionKeysDirectory = "$($EncryptionInfoDirectory.FullName)\DecryptionKeys" If ($DecryptionKeysDirectory.Exists -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($DecryptionKeysDirectory.FullName)} ForEach ($Item In ($ValuesToEncrypt.GetEnumerator())) { [String]$NewEncryptedString_Value = ($Item.Value) [Switch]$NewEncryptedString_Verbose = $False [Hashtable]$NewEncryptedStringParameters = @{} $NewEncryptedStringParameters.Add('Value', ($NewEncryptedString_Value)) $NewEncryptedStringParameters.Add('Verbose', ($NewEncryptedString_Verbose)) $NewEncryptedStringInfo = New-EncryptedString @NewEncryptedStringParameters [System.IO.FileInfo]$EncryptedDataAsJSONPath = "$($EncryptedDataDirectory.FullName)\$($Item.Key).json" [String]$EncryptedDataAsJSON = ConvertTo-JSON -InputObject ($NewEncryptedStringInfo | Select-Object -Property @('EncryptedData')) $Null = [System.IO.File]::WriteAllText(($EncryptedDataAsJSONPath.FullName), ($EncryptedDataAsJSON), ([System.Text.Encoding]::Default)) $EncryptedDataAsJSONHash = (Get-FileHash -Path ($EncryptedDataAsJSONPath.FullName) -Algorithm SHA256).Hash [System.IO.FileInfo]$DecryptionKeyAsJSONPath = "$($DecryptionKeysDirectory.FullName)\$($Item.Key).json" [String]$DecryptionKeyAsJSON = ConvertTo-JSON -InputObject ($NewEncryptedStringInfo | Select-Object -Property @('DecryptionKey')) $Null = [System.IO.File]::WriteAllText(($DecryptionKeyAsJSONPath.FullName), ($DecryptionKeyAsJSON), ([System.Text.Encoding]::Default)) [String]$DecryptionKeyAsJSONHash = (Get-FileHash -Path ($DecryptionKeyAsJSONPath.FullName) -Algorithm SHA256).Hash $Null = Add-Member -InputObject ($NewEncryptedStringInfo) -Name 'ValueName' -Value ($Item.Name) -MemberType NoteProperty $Null = Add-Member -InputObject ($NewEncryptedStringInfo) -Name 'EncryptedDataPath' -Value ($EncryptedDataAsJSONPath.FullName) -MemberType NoteProperty $Null = Add-Member -InputObject ($NewEncryptedStringInfo) -Name 'EncryptedDataHash' -Value ($EncryptedDataAsJSONHash) -MemberType NoteProperty $Null = Add-Member -InputObject ($NewEncryptedStringInfo) -Name 'DecryptionKeyPath' -Value ($DecryptionKeyAsJSONPath.FullName) -MemberType NoteProperty $Null = Add-Member -InputObject ($NewEncryptedStringInfo) -Name 'DecryptionKeyHash' -Value ($DecryptionKeyAsJSONHash) -MemberType NoteProperty $OutputObject += ($NewEncryptedStringInfo) } Write-Output -InputObject ($OutputObject) .NOTES Do you have processes or scripts that require you to provide a password? Against the desires of your security officer, do you have to save those passwords in plain text, in your scripts? .LINK https://www.sqlshack.com/how-to-secure-your-passwords-with-powershell/ #> [CmdletBinding(ConfirmImpact = 'Low', SupportsShouldProcess = $True)] Param ( [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [String]$Value, [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [ValidateScript({($_.Length -eq 16) -or ($_.Length -eq 24) -or ($_.Length -eq 32)})] [String]$EncryptionKey, [Parameter(Mandatory=$False, ParameterSetName = 'Export')] [Switch]$Export, [Parameter(Mandatory=$False, ParameterSetName = 'Export')] [ValidateNotNullOrEmpty()] [ValidatePattern('^.*\.xml$')] [System.IO.FileInfo]$ExportPath, [Parameter(Mandatory=$False, ParameterSetName = 'Export')] [Switch]$Force, [Parameter(Mandatory=$False, ParameterSetName = 'Export')] [Switch]$Unsecure, [Parameter(Mandatory=$False)] [Switch]$ContinueOnError ) Begin { [ScriptBlock]$ErrorHandlingDefinition = { If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message -Join "`r`n`r`n")"} Else {$ExceptionMessage = "$($_.Exception.Message)"} [String]$ErrorMessage = "[Error Message: $($ExceptionMessage)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]" If ($ContinueOnError.IsPresent -eq $True) { Write-Warning -Message ($ErrorMessage) } ElseIf ($ContinueOnError.IsPresent -eq $False) { Throw ($ErrorMessage) } } Try { $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} $DateTimeFileFormat = 'yyyyMMdd' ###20190403### [ScriptBlock]$GetDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} $TextInfo = (Get-Culture).TextInfo #Determine the date and time we executed the function $FunctionStartTime = (Get-Date) [String]$CmdletName = $MyInvocation.MyCommand.Name $LogMessage = "Function `'$($CmdletName)`' is beginning. Please Wait..." Write-Verbose -Message $LogMessage #Define Default Action Preferences $ErrorActionPreference = 'Stop' $LogMessage = "The following parameters and values were provided to the `'$($CmdletName)`' function." Write-Verbose -Message $LogMessage $FunctionProperties = Get-Command -Name $CmdletName $FunctionParameters = $FunctionProperties.Parameters.Keys ForEach ($Parameter In $FunctionParameters) { If (!([String]::IsNullOrEmpty($Parameter))) { $ParameterProperties = Get-Variable -Name $Parameter -ErrorAction SilentlyContinue $ParameterValueCount = $ParameterProperties.Value | Measure-Object | Select-Object -ExpandProperty Count If ($ParameterValueCount -gt 1) { $ParameterValueStringFormat = ($ParameterProperties.Value | ForEach-Object {"`"$($_)`""}) -Join "`r`n" $LogMessage = "$($ParameterProperties.Name):`r`n`r`n$($ParameterValueStringFormat)" } Else { $ParameterValueStringFormat = ($ParameterProperties.Value | ForEach-Object {"`"$($_)`""}) -Join ', ' $LogMessage = "$($ParameterProperties.Name): $($ParameterValueStringFormat)" } Switch ($ParameterProperties.Name) { {([String]::IsNullOrEmpty($_) -eq $False) -and ($_ -inotmatch "^Password$|^PW$|^Passphrase$|^.*Encryption.*$|^.*Key.*$")} { Write-Verbose -Message $LogMessage } } } } $LogMessage = "Execution of `'$($CmdletName)`' began on $($FunctionStartTime.ToString($DateTimeLogFormat))" Write-Verbose -Message $LogMessage } Catch { $ErrorHandlingDefinition.Invoke() } } Process { Try { Switch ($True) { {($PSBoundParameters.ContainsKey('EncryptionKey') -eq $False) -and ([String]::IsNullOrEmpty($EncryptionKey) -eq $True)} { $Null = Add-Type -AssemblyName 'System.Web' [String]$EncryptionKey = [System.Web.Security.Membership]::GeneratePassword(32, 16) [Byte[]]$Key = [System.Text.Encoding]::UTF8.GetBytes($EncryptionKey) $Null = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key) $KeyEncoded = [System.Convert]::ToBase64String($Key) } {($PSBoundParameters.ContainsKey('EncryptionKey') -eq $True) -and ([String]::IsNullOrEmpty($EncryptionKey) -eq $False)} { [Byte[]]$Key = [System.Text.Encoding]::UTF8.GetBytes($EncryptionKey) $Null = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key) $KeyEncoded = [System.Convert]::ToBase64String($Key) } } [String]$ConvertToSecureString_String = ($Value) [Switch]$ConvertToSecureString_AsPlainText = $True [Switch]$ConvertToSecureString_Force = $True [Hashtable]$ConvertToSecureStringParameters = @{} $ConvertToSecureStringParameters.Add('String', ($ConvertToSecureString_String)) $ConvertToSecureStringParameters.Add('AsPlainText', ($ConvertToSecureString_AsPlainText)) $ConvertToSecureStringParameters.Add('Force', ($ConvertToSecureString_Force)) [System.Security.SecureString]$ValueAsSecureString = ConvertTo-SecureString @ConvertToSecureStringParameters [System.Security.SecureString]$ConvertFromSecureString_SecureString = ($ValueAsSecureString) [Byte[]]$ConvertFromSecureString_Key = ($Key) [Hashtable]$ConvertFromSecureStringParameters = @{} $ConvertFromSecureStringParameters.Add('SecureString', ($ConvertFromSecureString_SecureString)) $ConvertFromSecureStringParameters.Add('Key', ($ConvertFromSecureString_Key)) [String]$EncryptedData = ConvertFrom-SecureString @ConvertFromSecureStringParameters [Hashtable]$OutputProperties = [Ordered]@{} $OutputProperties.Add('DecryptionKey', ($KeyEncoded)) $OutputProperties.Add('EncryptedData', ($EncryptedData)) [PSObject]$OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputProperties) Switch ($True) { {($Export.IsPresent)} { Switch ($True) { {($PSBoundParameters.ContainsKey('ExportPath') -eq $False) -and ($Null -ieq $ExportPath)} { [System.IO.FileInfo]$ExportPath = "$($Env:Temp.TrimEnd('\'))\$($CmdletName)\EncryptionInfo_$($GetDateTimeFileFormat.Invoke()).xml" } {$ExportPath.Directory.Exists -eq $False} { $Null = [System.IO.Directory]::CreateDirectory($ExportPath.Directory.FullName) } {($Unsecure.IsPresent -eq $True)} { $ExportableObject = $OutputObject } {($Unsecure.IsPresent -eq $False)} { $ExportableObject = $OutputObject | Select-Object -Property * -ExcludeProperty @('DecryptionKey') } } Switch ($True) { {((Test-Path -Path $ExportPath.FullName -ErrorAction SilentlyContinue) -eq $True) -and ($Force.IsPresent -eq $False)} { [String]$WarningMessage = "File `"$($ExportPath.FullName)`" already exists and will not be overwritten!" Write-Warning -Message ($WarningMessage) } {((Test-Path -Path $ExportPath.FullName -ErrorAction SilentlyContinue) -eq $True) -and ($Force.IsPresent -eq $True)} { [String]$WarningMessage = "File `"$($ExportPath.FullName)`" already exists and will be overwritten!" Write-Warning -Message ($WarningMessage) $Null = Export-CLIXML -InputObject ($ExportableObject) -Path ($ExportPath.FullName) -Depth 5 -Encoding Default -Force:($Force.IsPresent) } {((Test-Path -Path $ExportPath.FullName -ErrorAction SilentlyContinue) -eq $False)} { [String]$LogMessage = "File `"$($ExportPath.FullName)`" does not exist and will be created. Please Wait..." Write-Verbose -Message ($LogMessage) $Null = Export-CLIXML -InputObject ($ExportableObject) -Path ($ExportPath.FullName) -Depth 5 -Encoding Default -Force:($Force.IsPresent) } } } } } Catch { $ErrorHandlingDefinition.Invoke() } } End { Try { #Write .NET object returned by this function to the powershell pipeline Write-Output -InputObject ($OutputObject) #Determine the date and time the function completed execution $FunctionEndTime = (Get-Date) $LogMessage = "Execution of `'$($CmdletName)`' ended on $($FunctionEndTime.ToString($DateTimeLogFormat))" Write-Verbose -Message $LogMessage #Log the total script execution time $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) $LogMessage = "Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)" Write-Verbose -Message $LogMessage $LogMessage = "Function `'$($CmdletName)`' is completed." Write-Verbose -Message $LogMessage } Catch { $ErrorHandlingDefinition.Invoke() } } } #endregion |