PSaru.Security.psm1

Set-StrictMode -Version 'Latest'

function Get-RandomPassword {
    [OutputType('String')]
    [OutputType('System.Security.SecureString')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param (
        [int]$Length = 17,

        [string[]]$IncludeChars = @('~', '!', '@', '#', '$', '%', '^', '&', '*', '_', '-', '+', '=', '`', '|', '\', '(', ')', '{', '}', '[', ']', ':', '<', '>', ',', '.', '?', '/'),

        [string[]]$ExcludeChars = @(),

        [switch]$AsPlainText
    )

    65..90 | ForEach-Object {
        $IncludeChars += [char]$_
    }

    97..122 | ForEach-Object {
        $IncludeChars += [char]$_
    }

    $IncludeChars = $IncludeChars | Where-Object {$_ -NotIn $ExcludeChars}

    [System.Security.SecureString]$SecurePassword = New-Object -TypeName 'System.Security.SecureString'

    $Randomness = Get-CryptoRandomByte -Length $Length -Minimum 0 -Maximum ($IncludeChars.Length - 1)

    0..($Length - 1) | ForEach-Object {
        $SecurePassword.AppendChar( $IncludeChars[$Randomness[$_]])
    }

    if ($AsPlainText.IsPresent) {
        $ret = ConvertTo-PlainText -SecureString $SecurePassword
    } 
    else {
        $ret = $SecurePassword
    }

    $ret
}

function ConvertTo-PlainText {
    [OutputType('System.String')]
    [CmdletBinding()]
    param (        
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]                
        [System.Security.SecureString]$SecureString
    )
    
    PROCESS {
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
        $PlainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
        $PlainText
    }
}

function Get-StringHash {
    [OutputType('System.String')]
    [CmdletBinding()]
    param (        
        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatenotNullOrEmpty()]
        [string[]]$String,
        
        [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512', 'MACTripleDES', 'MD5', 'RIPEMD160')]
        [string]$Algorithm = 'SHA256',

        [Alias('Lower')]
        [switch]$LowerCase,

        [switch]$Group,

        [ValidateRange(1, 8)]
        [Int32]$GroupSize = 4,

        [ValidateNotNullOrEmpty()]
        [char]$GroupSepChar = '-'

    )
    
    PROCESS {            
        $String | ForEach-Object { 
            try {
                $Bytes = [System.Text.Encoding]::UTF8.GetBytes($String)
                $Stream = New-Object -TypeName 'System.IO.MemoryStream'
                $Stream.Write($Bytes, 0, $Bytes.Length)
                $Stream.Seek(0, 'Begin') | Out-Null
                $Hash = Get-FileHash -InputStream $Stream -Algorithm $Algorithm
                $Stream.Close()
                $Stream.Dispose()

                [string]$Ret = $Hash.Hash;

                if ($LowerCase.IsPresent) {
                    $Ret = $Ret.ToLower()
                }
                
                if ($Group.IsPresent) {
                    $tmp = ''
                    
                    for ($i = 0; $i -lt ($Ret.Length - $GroupSize); $i += $GroupSize) {
                        $tmp += $Ret.Substring($i, $GroupSize)
                        $tmp += $GroupSepChar
                    }
                    
                    $tmp += $Ret.Substring($i, $GroupSize)                        
                    
                    $Ret = $tmp
                }
                
                $Ret
            }
            finally {
                if ($Stream) {
                    $Stream.Close()
                    $Stream.Dispose()                
                }
            }            
        }
    }
}

function ConvertTo-ByteArray {
    [OutputType('System.Byte')]
    [CmdletBinding()]
    param (   
        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatenotNullOrEmpty()]
        [string[]]$Value
    )   

    PROCESS {
        foreach ($HexString in $Value  ) {
            if ($HexString -notmatch '^([0-9a-f][0-9a-f])+$') {
                if ($HexString.Length -gt 20) {
                    $HexString = $HexString.Substring(0, 20) + '...'
                }
                
                throw "Invalid hex string '$HexString'"
            }
            
            for ($i = 0; $i -lt $HexString.Length; $i += 2) {                
                [byte]::Parse($HexString.Substring($i, 2), [System.Globalization.NumberStyles]::HexNumber)
            }
        }
    }
}

function Get-CryptoRandomByte {
    [OutputType('System.Byte')]
    param (
        [Parameter(Mandatory = $true)]
        [Int32]$Length,

        [Alias('Min')]
        [Byte]$Minimum = 0,

        [Alias('Max')]
        [Byte]$Maximum = 255
    )

    [System.Security.Cryptography.RNGCryptoServiceProvider]$RandomNumberGenerator = New-Object -TypeName 'System.Security.Cryptography.RNGCryptoServiceProvider'    
    [Byte[]]$Bytes = New-Object -TypeName 'Byte[]' -ArgumentList 1024    
    $RandomNumberGenerator.GetBytes($Bytes)
    $Bytes | Where-Object {$_ -ge $Minimum -and $_ -le $Maximum} | Select-Object -First $Length
}