Public/Get-StringHash.ps1
|
class StringHashInfo { [string]$Algorithm [string]$Hash [string]$Text } <# .SYNOPSIS Calculate the hash value of a string. .DESCRIPTION Calculate the hash value of a string or derive a password hash with PBKDF2. .PARAMETER Algorithm The hash algorithm to use. `SHA512` is used by default. .PARAMETER Text The text of which is to be calculated. .PARAMETER Password The password of which is to be calculated. .PARAMETER Salt The salt value used for password hashing. The password path uses PBKDF2-SHA512 with 100,000 iterations. .NOTES The password path uses PBKDF2-SHA512 with 100,000 iterations and a 32-byte derived key. This is substantially stronger than a repeated fast hash such as MD5. .INPUTS A string for text hashing or a secure string for password hashing can be passed through the pipeline. .OUTPUTS Returns a `StringHashInfo` object for text hashing and a PBKDF2-SHA512 hash string for password hashing. .EXAMPLE Get-StringHash -Text 'This is a test text!' Determines the SHA512 hash value of the supplied text. .EXAMPLE Get-StringHash -Password ('P@$$w0rd!' | ConvertTo-SecureString -AsPlainText) -Salt 'vb23er45zu' Determines the password hash while applying the provided salt value. .EXAMPLE 'ForestHouse!1234', 'SoupBag471', 'DoorBell00815' | Get-StringHash -Algorithm MD5 Returns three `StringHashInfo` objects. .EXAMPLE 'ForestHouse!1234', 'SoupBag471', 'DoorBell00815' | ConvertTo-SecureString -AsPlainText | Get-StringHash -Salt 'qweasdyxc' Returns three password hashes. #> function Get-StringHash { [CmdletBinding(DefaultParameterSetName = 'Text')] param ( [Parameter(ParameterSetName = 'Text')] [ValidateSet('MD5', 'SHA512')] [String]$Algorithm = 'SHA512', [Parameter(ParameterSetName = 'Text', Mandatory, ValueFromPipeline)] [String]$Text, [Parameter(ParameterSetName = 'Password', Mandatory, ValueFromPipeline)] [SecureString]$Password, [Parameter(ParameterSetName = 'Password', Mandatory)] [String]$Salt ) begin { $My = [HashTable]::Synchronized(@{}) } process { if ($PSCmdlet.ParameterSetName -eq 'Password') { $My.Iterations = 100000 $My.KeyLength = 32 $My.BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password) try { $My.PasswordText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($My.BSTR) } finally { if ($My.BSTR -ne [IntPtr]::Zero) { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($My.BSTR) $My.BSTR = [IntPtr]::Zero } } $My.SaltBytes = [System.Text.Encoding]::UTF8.GetBytes($Salt) $My.Pbkdf2 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new( $My.PasswordText, $My.SaltBytes, $My.Iterations, [System.Security.Cryptography.HashAlgorithmName]::SHA512 ) try { $My.Result = [System.Convert]::ToBase64String($My.Pbkdf2.GetBytes($My.KeyLength)) } finally { $My.Pbkdf2.Dispose() } $My.PasswordText = $null return $My.Result } $My.Duration = 1 $My.Hash = $Text for ($i = 0; $i -lt $My.Duration; $i++) { $My.TextBytes = [System.Text.Encoding]::UTF8.GetBytes($My.Hash + $Salt) $My.HashBytes = switch ($Algorithm.ToUpperInvariant()) { 'MD5' { [System.Security.Cryptography.MD5]::HashData($My.TextBytes) break } 'SHA512' { [System.Security.Cryptography.SHA512]::HashData($My.TextBytes) break } default { throw "Unsupported hash algorithm: $Algorithm" } } $My.Hash = [System.Convert]::ToBase64String($My.HashBytes) } $My.Result = New-Object -TypeName StringHashInfo $My.Result.Algorithm = $Algorithm.ToUpper() $My.Result.Hash = $My.Hash $My.Result.Text = $Text return $My.Result } end { $My.Clear() $My = $null Remove-Variable -Name My -Force } } |