Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1
using namespace System.IO using namespace System.Web using namespace System.Text using namespace System.Net.Http using namespace System.Security using namespace System.Runtime.InteropServices Import-Module cliHelper.xconvert # .SYNOPSIS # xcrypt is a collection af all the basic Cryptography functions that you need. AES 128,192 or 256 etc. # .DESCRIPTION # xcrypt: xtended cryptography. you can do lots of cool stuff with this class. You just have to get creative with the methods. # + It can act as your Password manger. # + It can also be used as a simple chat bot (This is still in Beta) # .NOTES # [+] Most of the methods work. (Most). # [+] This file is over 4000 lines of code (All in One), so use regions code folding if your editor supports it. #Requires -Version 5.1 # Load all necessary dlls: $script:RuntimeDir = [Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory(); @( 'Microsoft.PowerShell.Commands.Utility' ).ForEach({ [void][System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($RuntimeDir, "$_.dll")) }) #region enums enum EncryptionScope { User # The encrypted data can be decrypted with the same user on any machine. Machine # The encrypted data can only be decrypted with the same user on the same machine it was encrypted on. } enum keyStoreMode { Vault KeyFile SecureString } enum KeyExportPolicy { NonExportable ExportableEncrypted Exportable } enum KeyProtection { None Protect ProtectHigh ProtectFingerPrint } enum KeyUsage { None EncipherOnly CRLSign CertSign KeyAgreement DataEncipherment KeyEncipherment NonRepudiation DigitalSignature DecipherOnly } enum X509ContentType { Unknown Cert SerializedCert Pfx PEM Pkcs12 SerializedStore Pkcs7 Authenticode } enum SdCategory { Token Password } enum ExpType { Milliseconds Years Months Days Hours Minutes Seconds } enum CertStoreName { MY ROOT TRUST CA } # Only Encryption algorithms that are widely trusted and used in real-world enum CryptoAlgorithm { AesGCM # AES-GCM (Galois/Counter Mode). A strong encryption on its own that doesn't necessarily with its built-in authentication functions. Its a mode of operation for AES that provides both confidentiality and authenticity for the encrypted data. GCM provides faster encryption and decryption compared to CBC mode and is widely used for secure communication, especially in VPN and TLS/SSL apps. ChaCha20 # ChaCha20 + SHA256 in this case. I would prefer ChaCha20Poly1305 but the Poly1305 class is still not working/usable. But no wories, ChaCha20 is like the salsa of the cryptography world, it's got the moves to keep your data secure and grooving to its own beat! :) Get it? [ref] to the dance-like steps performed in the algorithm's mixing process? Nevermind ... Its a symmetric key encryption algorithm, based on salsa20 algorithm. ChaCha20 provides the encryption, while Poly1305 (or SHA256 in this case) provides the authentication. This combination provides both confidentiality and authenticity for the encrypted data. RsaAesHMAC # RSA + AES + HMAC: This combination uses RSA for key exchange, AES for encryption, and HMAC (hash-based message authentication code) for authentication. This provides a secure mechanism for exchanging keys and encrypting data, as well as a way to verify the authenticity of the data. ie: By combining RSA and AES, one can take advantage of both algorithms' strengths: RSA is used to securely exchange the AES key, while AES is be used for the actual encryption and decryption of the data. This way, RSA provides security for key exchange, and AES provides fast encryption and decryption for the data. RsaECDSA # RSA + ECDSA (Elliptic Curve Digital Signature Algorithm) are public-key cryptography algorithms that are often used together. RSA can be used for encrypting data, while ECDSA can be used for digital signatures, providing both confidentiality and authenticity for the data. RsaOAEP # RSA-OAEP (Optimal Asymmetric Encryption Padding) } # System.Security.Cryptography.RSAEncryptionPadding Names enum RSAPadding { Pkcs1 OaepSHA1 OaepSHA256 OaepSHA384 OaepSHA512 } enum Compression { Gzip Deflate ZLib # Zstd # Todo: Add Zstandard. (The one from facebook. or maybe zstd-sharp idk. I just can't find a way to make it work in powershell! no dll nothing!) } enum CredFlags { None = 0x0 PromptNow = 0x2 UsernameTarget = 0x4 } enum CredType { Generic = 1 DomainPassword = 2 DomainCertificate = 3 DomainVisiblePassword = 4 GenericCertificate = 5 DomainExtended = 6 Maximum = 7 MaximumEx = 1007 # (Maximum + 1000) } enum CredentialPersistence { Session = 1 LocalComputer = 2 Enterprise = 3 } #endregion enums class InvalidArgumentException : System.Exception { [string]$paramName [string]$Message InvalidArgumentException() { $this.message = 'Invalid argument' } InvalidArgumentException([string]$paramName) { $this.paramName = $paramName $this.message = "Invalid argument: $paramName" } InvalidArgumentException([string]$paramName, [string]$message) { $this.paramName = $paramName $this.message = $message } } # Static class for calling the native credential functions class CredentialNotFoundException : System.Exception, System.Runtime.Serialization.ISerializable { [string]$Message; [Exception]$InnerException; hidden $Info; hidden $Context CredentialNotFoundException() { $this.Message = 'CredentialNotFound' } CredentialNotFoundException([string]$message) { $this.Message = $message } CredentialNotFoundException([string]$message, [Exception]$InnerException) { ($this.Message, $this.InnerException) = ($message, $InnerException) } CredentialNotFoundException([System.Runtime.Serialization.SerializationInfo]$info, [System.Runtime.Serialization.StreamingContext]$context) { ($this.Info, $this.Context) = ($info, $context) } } class IntegrityCheckFailedException : System.Exception { [string]$Message; [Exception]$InnerException; IntegrityCheckFailedException() { } IntegrityCheckFailedException([string]$message) { $this.Message = $message } IntegrityCheckFailedException([string]$message, [Exception]$innerException) { $this.Message = $message; $this.InnerException = $innerException } } class InvalidPasswordException : System.Exception { [string]$Message; [string]hidden $Passw0rd; [securestring]hidden $Password; [System.Exception]$InnerException InvalidPasswordException() { $this.Message = "Invalid password" } InvalidPasswordException([string]$Message) { $this.message = $Message } InvalidPasswordException([string]$Message, [string]$Passw0rd) { ($this.message, $this.Passw0rd, $this.InnerException) = ($Message, $Passw0rd, [System.Exception]::new($Message)) } InvalidPasswordException([string]$Message, [securestring]$Password) { ($this.message, $this.Password, $this.InnerException) = ($Message, $Password, [System.Exception]::new($Message)) } InvalidPasswordException([string]$Message, [string]$Passw0rd, [System.Exception]$InnerException) { ($this.message, $this.Passw0rd, $this.InnerException) = ($Message, $Passw0rd, $InnerException) } InvalidPasswordException([string]$Message, [securestring]$Password, [System.Exception]$InnerException) { ($this.message, $this.Password, $this.InnerException) = ($Message, $Password, $InnerException) } } class cPsObject : PsObject { cPsObject([System.Object]$Object) { $types = (($Object | Get-Member).Typename | Sort-Object -Unique) $ogtyp = if ($types.count -eq 1) { $types -as 'type' } else { $Object.GetType() } $b64sb = [convert]::ToBase64String($(if ($types.Equals("System.Byte")) { [byte[]]$Object } else { $Object | xconvert ToBytes })) $this.PsObject.properties.add([psscriptproperty]::new('Type', [scriptblock]::Create("[Type]'$ogtyp'"))) $this.PsObject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create("[Convert]::FromBase64String('$b64sb')"))) $this.PsObject.properties.add([psscriptproperty]::new('SecScope', [scriptblock]::Create('[EncryptionScope]::User'))) $this.PsObject.Methods.Add( [psscriptmethod]::new( 'Protect', { $_bytes = $this.Bytes; $Entropy = [System.Text.Encoding]::UTF8.GetBytes([xcrypt]::GetUniqueMachineId())[0..15] $_bytes = Protect-Data -Bytes $_bytes -Scope $this.SecScope -Entropy $Entropy $this.PsObject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create($_bytes))) } ) ) $this.PsObject.Methods.Add( [psscriptmethod]::new( 'UnProtect', { $_bytes = $this.Bytes; $Entropy = [System.Text.Encoding]::UTF8.GetBytes([xcrypt]::GetUniqueMachineId())[0..15] $_bytes = UnProtect-Data -Bytes $_bytes -Entropy $Entropy -Scope $this.SecScope $this.PsObject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create($_bytes))) } ) ) $this.PsObject.Methods.Add( [psscriptmethod]::new( 'Tostring', { return $this.PsObject.properties.value[0].name } ) ) } } #region xcrypt class xcrypt { static hidden [string] $caller [ValidateNotNull()][byte[]]hidden $_salt [ValidateNotNull()][byte[]]hidden $_bytes static [ValidateNotNull()][EncryptionScope] $EncryptionScope [ValidateNotNull()][securestring]hidden $_Password [ValidateNotNull()][CryptoAlgorithm]hidden $_Algorithm xcrypt() {} static [string] GetRandomName() { return [xcrypt]::GetRandomName((Get-Random -min 16 -max 80)); } static [string] GetRandomName([int]$Length) { return [string][xcrypt]::GetRandomName($Length, $Length); } static [string] GetRandomName([bool]$IncludeNumbers) { $Length = Get-Random -min 16 -max 80 return [string][xcrypt]::GetRandomName($Length, $Length, $IncludeNumbers); } static [string] GetRandomName([int]$Length, [bool]$IncludeNumbers) { return [string][xcrypt]::GetRandomName($Length, $Length, $IncludeNumbers); } static [string] GetRandomName([int]$minLength, [int]$maxLength) { return [string][xcrypt]::GetRandomName($minLength, $maxLength, $false); } static [string] GetRandomName([int]$minLength, [int]$maxLength, [bool]$IncludeNumbers) { [int]$iterations = 2; $MinrL = 3; $MaxrL = 999 #Gotta have some restrictions, or one typo could slow down an entire script. if ($minLength -lt $MinrL) { Write-Warning "Length is below the Minimum required 'String Length'. Try $MinrL or greater." ; Break } if ($maxLength -gt $MaxrL) { Write-Warning "Length is greater the Maximum required 'String Length'. Try $MaxrL or lower." ; Break } $samplekeys = if ($IncludeNumbers) { [string]::Join('', ([int[]](97..122) | ForEach-Object { [string][char]$_ }) + (0..9)) } else { [string]::Join('', ([int[]](97..122) | ForEach-Object { [string][char]$_ })) } return [string][xcrypt]::GetRandomSTR($samplekeys, $iterations, $minLength, $maxLength); } static [byte[]] GetDerivedBytes() { return [xcrypt]::GetDerivedBytes(16) } static [byte[]] GetDerivedBytes([int]$Length) { return [xcrypt]::GetDerivedBytes(([xcrypt]::GetRandomName(16) | xconvert ToSecurestring), $Length) } static [byte[]] GetDerivedBytes([securestring]$password) { return [xcrypt]::GetDerivedBytes($password, 16) } static [byte[]] GetDerivedBytes([securestring]$password, [int]$Length) { $pswd = $(switch ([xcrypt]::EncryptionScope.ToString()) { "Machine" { [System.Text.Encoding]::UTF8.GetBytes([xcrypt]::GetUniqueMachineId()) } Default { [convert]::FromBase64String("hsKgmva9wZoDxLeREB1udw==") } } ) | xconvert ToSecurestring $s6lt = [System.Security.Cryptography.Rfc2898DeriveBytes]::new($password, [System.Text.Encoding]::UTF8.GetBytes(($password | xconvert ToString))).GetBytes(16) return [xcrypt]::GetDerivedBytes($pswd, $s6lt, $Length) } static [byte[]] GetDerivedBytes([securestring]$password, [byte[]]$salt, [int]$Length) { return [System.Security.Cryptography.Rfc2898DeriveBytes]::new($password, $salt, 1000).GetBytes($Length); } static [byte[]] GetKey() { return [xcrypt]::GetKey(16); } static [byte[]] GetKey([int]$Length) { return [xcrypt]::GetKey(([xcrypt]::GeneratePassword() | xconvert ToSecurestring), $Length) } static [byte[]] GetKey([securestring]$password) { return [xcrypt]::GetKey($password, 16) } static [byte[]] GetKey([securestring]$password, [int]$Length) { return [xcrypt]::GetDerivedBytes($password, $Length) } static [byte[]] GetKey([securestring]$password, [byte[]]$salt) { return [xcrypt]::GetKey($password, $salt, 16) } static [byte[]] GetKey([securestring]$password, [byte[]]$salt, [int]$Length) { return [xcrypt]::GetDerivedBytes($password, $salt, $Length) } # can be used to generate random IV static [byte[]] GetRandomEntropy() { [byte[]]$entropy = [byte[]]::new(16); [void][System.Security.Cryptography.RNGCryptoServiceProvider]::new().GetBytes($entropy) return $entropy; } # Uses a cryptographic hash function (SHA-256) to generate a unique machine ID static hidden [string] GetRandomSTR([string]$InputSample, [int]$iterations, [int]$minLength, [int]$maxLength) { if ($maxLength -lt $minLength) { throw [System.ArgumentOutOfRangeException]::new('MinLength', "'MaxLength' cannot be less than 'MinLength'") } if ($iterations -le 0) { Write-Warning 'Negative and Zero Iterations are NOT Possible!'; return [string]::Empty } [char[]]$chars = [char[]]::new($InputSample.Length); $chars = $InputSample.ToCharArray(); $Keys = [System.Collections.Generic.List[string]]::new(); $rand = [Random]::new(); [int]$size = $rand.Next([int]$minLength, [int]$maxLength); for ($i = 0; $i -lt $iterations; $i++) { [byte[]] $data = [Byte[]]::new(1); $crypto = [System.Security.Cryptography.RNGCryptoServiceProvider]::new(); $data = [Byte[]]::new($size); $crypto.GetNonZeroBytes($data); $result = [System.Text.StringBuilder]::new($size); foreach ($b In $data) { $result.Append($chars[$b % ($chars.Length - 1)]) }; [void]$Keys.Add($result.ToString()); } $STR = [string]::Join('', $keys) if ($STR.Length -gt $maxLength) { $STR = $STR.Substring(0, $maxLength); } return $STR; } static [string] GeneratePassword() { return [string][xcrypt]::GeneratePassword(19); } static [string] GeneratePassword([int]$Length) { return [string][xcrypt]::GeneratePassword($Length, $false, $false, $false, $false); } static [string] GeneratePassword([int]$Length, [bool]$StartWithLetter) { return [string][xcrypt]::GeneratePassword($Length, $StartWithLetter, $false, $false, $false); } static [string] GeneratePassword([int]$Length, [bool]$StartWithLetter, [bool]$NoSymbols, [bool]$UseAmbiguousCharacters, [bool]$UseExtendedAscii) { # https://stackoverflow.com/questions/55556/characters-to-avoid-in-automatically-generated-passwords [string]$possibleCharacters = [char[]](33..126 + 161..254); $MinrL = 14; $MaxrL = 999 # Gotta have some restrictions, or one typo could endup creating insanely long or small Passwords, ex 30000 intead of 30. if ($Length -lt $MinrL) { Write-Warning "Length is below the Minimum required 'Password Length'. Try $MinrL or greater."; Break } if ($Length -gt $MaxrL) { Write-Warning "Length is greater the Maximum required 'Password Length'. Try $MaxrL or lower."; Break } # Warn the user if they've specified mutually-exclusive options. if ($NoSymbols -and $UseExtendedAscii) { Write-Warning 'The -NoSymbols parameter was also specified. No extended ASCII characters will be used.' } do { $Passw0rd = [string]::Empty; $x = $null; $r = 0 #This person Wants a really good password, so We retry Until we get a 60% strong password. do { do { do { do { do { $x = [int][char][string][xcrypt]::GetRandomSTR($possibleCharacters, 1, 1, 1); # Write-Verbose "Use character: $([char]$x) : $x" } While ($x -eq 127 -Or (!$UseExtendedAscii -and $x -gt 127)) # The above Do..While loop does this: # 1. Don't allow ASCII 127 (delete). # 2. Don't allow extended ASCII, unless the user wants it. } While (!$UseAmbiguousCharacters -and ($x -In @(49, 73, 108, 124, 48, 79))) # The above loop disallows 1 (ASCII 49), I (73), l (108), # | (124), 0 (48) or O (79) -- unless the user wants those. } While ($NoSymbols -and ($x -lt 48 -Or ($x -gt 57 -and $x -lt 65) -Or ($x -gt 90 -and $x -lt 97) -Or $x -gt 122)) # If the -NoSymbols parameter was specified, this loop will ensure # that the character is neither a symbol nor in the extended ASCII # character set. } While ($r -eq 0 -and $StartWithLetter -and !(($x -ge 65 -and $x -le 90) -Or ($x -ge 97 -and $x -le 122))) # If the -StartWithLetter parameter was specified, this loop will make # sure that the first character is an upper- or lower-case letter. $Passw0rd = $Passw0rd.Trim() $Passw0rd += [string][char]$x; $r++ } until ($Passw0rd.length -eq $Length) } until ([int][xcrypt]::GetPasswordStrength($Passw0rd) -gt 60) return $Passw0rd; } [int] static GetPasswordStrength([string]$passw0rd) { # Inspired by: https://www.security.org/how-secure-is-my-password/ $passwordDigits = [System.Text.RegularExpressions.Regex]::new("\d", [System.Text.RegularExpressions.RegexOptions]::Compiled); $passwordNonWord = [System.Text.RegularExpressions.Regex]::new("\W", [System.Text.RegularExpressions.RegexOptions]::Compiled); $passwordUppercase = [System.Text.RegularExpressions.Regex]::new("[A-Z]", [System.Text.RegularExpressions.RegexOptions]::Compiled); $passwordLowercase = [System.Text.RegularExpressions.Regex]::new("[a-z]", [System.Text.RegularExpressions.RegexOptions]::Compiled); [int]$strength = 0; $digits = $passwordDigits.Matches($passw0rd); $NonWords = $passwordNonWord.Matches($passw0rd); $Uppercases = $passwordUppercase.Matches($passw0rd); $Lowercases = $passwordLowercase.Matches($passw0rd); if ($digits.Count -ge 2) { $strength += 10 }; if ($digits.Count -ge 5) { $strength += 10 }; if ($NonWords.Count -ge 2) { $strength += 10 }; if ($NonWords.Count -ge 5) { $strength += 10 }; if ($passw0rd.Length -gt 8) { $strength += 10 }; if ($passw0rd.Length -ge 16) { $strength += 10 }; if ($Lowercases.Count -ge 2) { $strength += 10 }; if ($Lowercases.Count -ge 5) { $strength += 10 }; if ($Uppercases.Count -ge 2) { $strength += 10 }; if ($Uppercases.Count -ge 5) { $strength += 10 }; return $strength; } static [bool] IsBase64String([string]$base64) { return $(try { [void][Convert]::FromBase64String($base64); $true } catch { $false }) } static [bool] IsValidAES([System.Security.Cryptography.Aes]$aes) { return [bool]$(try { [xcrypt]::CheckProps($aes); $? } catch { $false }) } static [void] CheckProps([System.Security.Cryptography.Aes]$Aes) { $MissingProps = @(); $throw = $false Write-Verbose "$([xcrypt]::caller) [+] Checking Encryption Properties ... $(('Mode','Padding', 'keysize', 'BlockSize') | ForEach-Object { if ($null -eq $Aes.Algo.$_) { $MissingProps += $_ } }; if ($MissingProps.Count -eq 0) { "Done. All AES Props are Good." } else { $throw = $true; "System.ArgumentNullException: $([string]::Join(', ', $MissingProps)) cannot be null." } )" if ($throw) { throw [System.ArgumentNullException]::new([string]::Join(', ', $MissingProps)) } } static [string] GetResolvedPath([string]$Path) { return [xcrypt]::GetResolvedPath($((Get-Variable ExecutionContext).Value.SessionState), $Path) } static [string] GetResolvedPath([System.Management.Automation.SessionState]$session, [string]$Path) { $paths = $session.Path.GetResolvedPSPathFromPSPath($Path); if ($paths.Count -gt 1) { throw [System.IO.IOException]::new([string]::Format([cultureinfo]::InvariantCulture, "Path {0} is ambiguous", $Path)) } elseif ($paths.Count -lt 1) { throw [System.IO.IOException]::new([string]::Format([cultureinfo]::InvariantCulture, "Path {0} not Found", $Path)) } return $paths[0].Path } static [string] GetUnResolvedPath([string]$Path) { return [xcrypt]::GetUnResolvedPath($((Get-Variable ExecutionContext).Value.SessionState), $Path) } static [string] GetUnResolvedPath([System.Management.Automation.SessionState]$session, [string]$Path) { return $session.Path.GetUnresolvedProviderPathFromPSPath($Path) } static [System.Type] CreateEnum([string]$Name, [bool]$IsPublic, [string[]]$Members) { # Example: # $MacMseries = [xcrypt]::CreateEnum('Mseries', $true, ('M1', 'M2', 'M3')) # $MacMseries::M1 | gm # Todo: Explore more about [System.Reflection.Emit.EnumBuilder], so we can add more features. ex: Flags, instead of [string[]]$Members we can have [hastable]$Members etc. try { if ([string]::IsNullOrWhiteSpace($Name)) { throw [InvalidArgumentException]::new('Name', 'Name can not be null or space') } $DynAssembly = [System.Reflection.AssemblyName]::new("EmittedEnum") $AssmBuilder = [System.Reflection.Emit.AssemblyBuilder]::DefineDynamicAssembly($DynAssembly, ([System.Reflection.Emit.AssemblyBuilderAccess]::Save -bor [System.Reflection.Emit.AssemblyBuilderAccess]::Run)) # Only run in memory $ModulBuildr = $AssmBuilder.DefineDynamicModule("DynamicModule") $type_attrib = if ($IsPublic) { [System.Reflection.TypeAttributes]::Public }else { [System.Reflection.TypeAttributes]::NotPublic } $enumBuilder = [System.Reflection.Emit.EnumBuilder]$ModulBuildr.DefineEnum($name, $type_attrib, [System.Int32]); for ($i = 0; $i -lt $Members.count; $i++) { [void]$enumBuilder.DefineLiteral($Members[$i], $i) } [void]$enumBuilder.CreateType() } catch { throw $_ } return ($Name -as [Type]) } static [System.Security.Cryptography.Aes] GetAes() { return [xcrypt]::GetAes(1) } static [System.Security.Cryptography.Aes] GetAes([int]$Iterations) { $salt = $null; $password = $null; Set-Variable -Name password -Scope Local -Visibility Private -Option Private -Value $([xcrypt]::GeneratePassword() | xconvert ToSecurestring); Set-Variable -Name salt -Scope Local -Visibility Private -Option Private -Value $([xcrypt]::GetDerivedBytes(16)); return [xcrypt]::GetAes($password, $salt, $Iterations) } static [System.Security.Cryptography.Aes] GetAes([securestring]$password, [byte[]]$salt, [int]$iterations) { $aes = $null; $M = $null; $P = $null; $k = $null; Set-Variable -Name aes -Scope Local -Visibility Private -Option Private -Value $([System.Security.Cryptography.AesManaged]::new()); #Note: 'Zeros' Padding was avoided, see: https://crypto.stackexchange.com/questions/1486/how-to-choose-a-padding-mode-with-aes # Personally I prefer PKCS7 as the best padding. for ($i = 1; $i -le $iterations; $i++) { ($M, $P, $k) = ((Get-Random ('ECB', 'CBC')), (Get-Random ('PKCS7', 'ISO10126', 'ANSIX923')), (Get-Random (128, 192, 256))) } $aes.Mode = & ([scriptblock]::Create("[System.Security.Cryptography.CipherMode]::$M")); $aes.Padding = & ([scriptblock]::Create("[System.Security.Cryptography.PaddingMode]::$P")); $aes.keysize = $k; $aes.Key = [xcrypt]::GetKey($password, $salt); $aes.IV = [xcrypt]::GetRandomEntropy(); return $aes } # Use a cryptographic hash function (SHA-256) to generate a unique machine ID static [string] GetUniqueMachineId() { $Id = [string]($Env:MachineId) $vp = (Get-Variable VerbosePreference).Value try { Set-Variable VerbosePreference -Value $([System.Management.Automation.ActionPreference]::SilentlyContinue) $sha256 = [System.Security.Cryptography.SHA256]::Create() $HostOS = $(if ($(Get-Variable PSVersionTable -Value).PSVersion.Major -le 5 -or $(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }); if ($HostOS -eq "Windows") { if ([string]::IsNullOrWhiteSpace($Id)) { $machineId = Get-CimInstance -ClassName Win32_ComputerSystemProduct | Select-Object -ExpandProperty UUID Set-Item -Path Env:\MachineId -Value $([convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($machineId)))); } $Id = [string]($Env:MachineId) } elseif ($HostOS -eq "Linux") { # $Id = (sudo cat /sys/class/dmi/id/product_uuid).Trim() # sudo prompt is a nono # Lets use mac addresses $Id = ([string[]]$(ip link show | grep "link/ether" | awk '{print $2}') -join '-').Trim() $Id = [convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Id))) } elseif ($HostOS -eq "macOS") { $Id = (system_profiler SPHardwareDataType | Select-String "UUID").Line.Split(":")[1].Trim() $Id = [convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Id))) } else { throw "Error: HostOS = '$HostOS'. Could not determine the operating system." } } catch { throw $_ } finally { $sha256.Clear(); $sha256.Dispose() Set-Variable VerbosePreference -Value $vp } return $Id } static [string] Get_Host_Os() { # Todo: Should return one of these: [Enum]::GetNames([System.PlatformID]) return $(if ($(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }) } static [IO.DirectoryInfo] Get_dataPath([string]$appName, [string]$SubdirName) { $_Host_OS = [xcrypt]::Get_Host_Os() $dataPath = if ($_Host_OS -eq 'Windows') { [System.IO.DirectoryInfo]::new([IO.Path]::Combine($Env:HOME, "AppData", "Roaming", $appName, $SubdirName)) } elseif ($_Host_OS -in ('Linux', 'MacOs')) { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } elseif ($_Host_OS -eq 'Unknown') { try { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } catch { Write-Warning "Could not resolve chat data path" Write-Warning "HostOS = '$_Host_OS'. Could not resolve data path." [System.IO.Directory]::CreateTempSubdirectory(($SubdirName + 'Data-')) } } else { throw [InvalidOperationException]::new('Could not resolve data path. Get_Host_OS FAILED!') } if (!$dataPath.Exists) { [xcrypt]::Create_Dir($dataPath) } return $dataPath } static [void] Create_Dir([string]$Path) { [xcrypt]::Create_Dir([System.IO.DirectoryInfo]::new($Path)) } static [void] Create_Dir([System.IO.DirectoryInfo]$Path) { [ValidateNotNullOrEmpty()][System.IO.DirectoryInfo]$Path = $Path $nF = @(); $p = $Path; while (!$p.Exists) { $nF += $p; $p = $p.Parent } [Array]::Reverse($nF); $nF | ForEach-Object { $_.Create(); Write-Verbose "Created $_" } } [securestring] static GetPassword() { $ThrowOnFailure = $true return [xcrypt]::GetPassword($ThrowOnFailure); } [securestring] static GetPassword([string]$Prompt) { return [xcrypt]::GetPassword($Prompt, $true) } [securestring] static GetPassword([bool]$ThrowOnFailure) { return [xcrypt]::GetPassword("Password", $ThrowOnFailure) } static [securestring] GetPassword([string]$Prompt, [bool]$ThrowOnFailure) { if ([xcrypt]::EncryptionScope.ToString() -eq "Machine") { return ([xcrypt]::GetUniqueMachineId() | xconvert ToSecurestring) } else { $pswd = [SecureString]::new(); $_caller = 'PasswordManager'; if ([xcrypt]::caller) { $_caller = [xcrypt]::caller } Set-Variable -Name pswd -Scope Local -Visibility Private -Option Private -Value $(Read-Host -Prompt "$_caller $Prompt" -AsSecureString); if ($ThrowOnFailure -and ($null -eq $pswd -or $([string]::IsNullOrWhiteSpace(($pswd | xconvert ToString))))) { throw [InvalidPasswordException]::new("Please Provide a Password that isn't Null or WhiteSpace.", $pswd, [System.ArgumentNullException]::new("Password")) } return $pswd; } } static [void] ValidateCompression([string]$Compression) { if ($Compression -notin ([Enum]::GetNames('Compression' -as 'Type'))) { Throw [System.InvalidCastException]::new("The name '$Compression' is not a valid [Compression]`$typeName.") }; } } #endregion xcrypt #region GitHub class GitHub { static $webSession static [string] $UserName static hidden [bool] $IsInteractive = $false static hidden [string] $TokenFile = [GitHub]::GetTokenFile() static [PSObject] createSession() { return [Github]::createSession([Github]::UserName) } static [PSObject] createSession([string]$UserName) { [GitHub]::SetToken() return [GitHub]::createSession($UserName, [GitHub]::GetToken()) } static [Psobject] createSession([string]$GitHubUserName, [securestring]$clientSecret) { [ValidateNotNullOrEmpty()][string]$GitHubUserName = $GitHubUserName [ValidateNotNullOrEmpty()][string]$GithubToken = $GithubToken = $([securestring]$clientSecret | xconvert ToString) $encodedAuth = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($GitHubUserName):$($GithubToken)")) $web_session = New-Object Microsoft.PowerShell.Commands.WebRequestSession [void]$web_session.Headers.Add('Authorization', "Basic $($encodedAuth)") [void]$web_session.Headers.Add('Accept', 'application/vnd.github.v3+json') [GitHub]::webSession = $web_session return $web_session } static [void] SetToken() { [GitHub]::SetToken(((Read-Host -Prompt "[GitHub] Paste/write your api token" -AsSecureString) | xconvert ToString), $(Read-Host -Prompt "[GitHub] Paste/write a Password to encrypt the token" -AsSecureString)) } static [void] SetToken([string]$token, [securestring]$password) { if (![IO.File]::Exists([GitHub]::TokenFile)) { New-Item -Type File -Path ([GitHub]::TokenFile) -Force | Out-Null } [IO.File]::WriteAllText([GitHub]::TokenFile, [convert]::ToBase64String([AesGCM]::Encrypt([system.Text.Encoding]::UTF8.GetBytes($token), $password)), [System.Text.Encoding]::UTF8); } static [securestring] GetToken() { $sectoken = $null; $session_pass = '123' | xconvert ToSecurestring; try { if ([GitHub]::IsInteractive) { if ([string]::IsNullOrWhiteSpace((Get-Content ([GitHub]::TokenFile) -ErrorAction Ignore))) { Write-Host "[GitHub] You'll need to set your api token first. This is a One-Time Process :)" -ForegroundColor Green [GitHub]::SetToken() Write-Host "[GitHub] Good, now let's use the api token :)" -ForegroundColor DarkGreen } elseif ([GitHub]::ValidateBase64String([IO.File]::ReadAllText([GitHub]::TokenFile))) { Write-Host "[GitHub] Encrypted token found in file: $([GitHub]::TokenFile)" -ForegroundColor DarkGreen } else { throw [System.Exception]::New("Unable to read token file!") } $session_pass = Read-Host -Prompt "[GitHub] Input password to use your token" -AsSecureString } else { #Fix: Temporary Workaround: Thisz a pat from one of my GitHub a/cs.It Can only read/write gists. Will expire on 1/1/2025. DoNot Abuse this or I'll take it down!! $et = "OOLqqov4ugMQAtFcWqbzRwNBD65uf9JOZ+jzx1RtcHAZtnKaq1zkIpBcuv1MQfOkvIr/V066Zgsaq5Gka+VhlbqhV8apm8zcQomYjYqLaECKAonFeeo9MqvaP1F2VLgXokrxD1M6weLwS7KC+dyvAgv10IEvLzWFMw==" [GitHub]::SetToken([convert]::ToBase64String([AesGCM]::Decrypt([convert]::FromBase64String($et), $session_pass)), $session_pass) } $sectoken = [system.Text.Encoding]::UTF8.GetString( [AesGCM]::Decrypt([Convert]::FromBase64String([IO.File]::ReadAllText([GitHub]::GetTokenFile())), $session_pass) ) | xconvert ToSecurestring } catch { throw $_ } return $sectoken } static [PsObject] GetUserInfo([string]$UserName) { if ([string]::IsNullOrWhiteSpace([GitHub]::userName)) { [GitHub]::createSession() } $response = Invoke-RestMethod -Uri "https://api.github.com/user/$UserName" -WebSession ([GitHub]::webSession) -Method Get -Verbose:$false return $response } static [PsObject] GetGist([uri]$Uri) { $l = [GistFile]::Create($Uri) return [GitHub]::GetGist($l.Owner, $l.Id) } static [PsObject] GetGist([string]$UserName, [string]$GistId) { $t = [GitHub]::GetToken() if ($null -eq ([GitHub]::webSession)) { [GitHub]::webSession = $(if ($null -eq $t) { [GitHub]::createSession($UserName) } else { [GitHub]::createSession($UserName, $t) } ) } if (!((Test-Connection github.com -Count 1).status -eq "Success")) { throw [System.Net.NetworkInformation.PingException]::new("PingException, PLease check your connection!"); } if ([string]::IsNullOrWhiteSpace($GistId) -or $GistId -eq '*') { return Get-Gists -UserName $UserName -SecureToken $t } return Invoke-RestMethod -Uri "https://api.github.com/gists/$GistId" -WebSession ([GitHub]::webSession) -Method Get -Verbose:$false } Static [string] GetGistContent([string]$FileName, [uri]$GistUri) { return [GitHub]::GetGist($GistUri).files.$FileName.content } static [PsObject] CreateGist([string]$description, [array]$files) { $url = 'https://api.github.com/gists' $body = @{ description = $description files = @{} } foreach ($file in $files) { $body.files[$file.Name] = @{ content = $file.Content } } $response = Invoke-RestMethod -Uri $url -WebSession ([GitHub]::webSession) -Method Post -Body ($body | ConvertTo-Json) -Verbose:$false return $response } static [PsObject] UpdateGist([GistFile]$gist, [string]$NewContent) { return '' } static [string] GetTokenFile() { if (![IO.File]::Exists([GitHub]::TokenFile)) { [GitHub]::TokenFile = [IO.Path]::Combine([GitHub]::Get_dataPath('Github', 'clicache'), "token"); } return [GitHub]::TokenFile } static [PsObject] GetUserRepositories() { if ($null -eq [GitHub]::webSession) { [Github]::createSession() } $response = Invoke-RestMethod -Uri 'https://api.github.com/user/repos' -WebSession ([GitHub]::webSession) -Method Get -Verbose:$false return $response } static [psobject] ParseLink([string]$text, [bool]$throwOnFailure) { [ValidateNotNullOrEmpty()][string]$text = $text $uri = $text -as 'Uri'; if ($uri -isnot [Uri] -and $throwOnFailure) { throw [System.InvalidOperationException]::New("Could not create uri from text '$text'.") }; $Scheme = $uri.Scheme if ([regex]::IsMatch($text, '^(\/[a-zA-Z0-9_-]+)+|([a-zA-Z]:\\(((?![<>:"\/\\|?*]).)+\\?)*((?![<>:"\/\\|?*]).)+)$')) { if ($text.ToCharArray().Where({ $_ -in [IO.Path]::InvalidPathChars }).Count -eq 0) { $Scheme = 'file' } else { Write-Debug "'$text' has invalidPathChars in it !" -Debug } } $IsValid = $Scheme -in @('file', 'https') $IsGistUrl = [Regex]::IsMatch($text, 'https?://gist\.github\.com/\w+/[0-9a-f]+') $OutptObject = [pscustomobject]@{ FullName = $text Scheme = [PSCustomObject]@{ Name = $Scheme IsValid = $IsValid IsGistUrl = $IsGistUrl } } return $OutptObject } static [string] Get_Host_Os() { # Should return one of these: [Enum]::GetNames([System.PlatformID]) return $(if ($(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }) } static [IO.DirectoryInfo] Get_dataPath([string]$appName, [string]$SubdirName) { $_Host_OS = [GitHub]::Get_Host_Os() $dataPath = if ($_Host_OS -eq 'Windows') { [System.IO.DirectoryInfo]::new([IO.Path]::Combine($Env:HOME, "AppData", "Roaming", $appName, $SubdirName)) } elseif ($_Host_OS -in ('Linux', 'MacOs')) { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } elseif ($_Host_OS -eq 'Unknown') { try { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } catch { Write-Warning "Could not resolve chat data path" Write-Warning "HostOS = '$_Host_OS'. Could not resolve data path." [System.IO.Directory]::CreateTempSubdirectory(($SubdirName + 'Data-')) } } else { throw [InvalidOperationException]::new('Could not resolve data path. Get_Host_OS FAILED!') } if (!$dataPath.Exists) { [GitHub]::Create_Dir($dataPath) } return $dataPath } static [void] Create_Dir([string]$Path) { [GitHub]::Create_Dir([System.IO.DirectoryInfo]::new($Path)) } static [void] Create_Dir([System.IO.DirectoryInfo]$Path) { [ValidateNotNullOrEmpty()][System.IO.DirectoryInfo]$Path = $Path $nF = @(); $p = $Path; while (!$p.Exists) { $nF += $p; $p = $p.Parent } [Array]::Reverse($nF); $nF | ForEach-Object { $_.Create(); Write-Verbose "Created $_" } } static [bool] ValidateBase64String([string]$base64) { return $(try { [void][Convert]::FromBase64String($base64); $true } catch { $false }) } static [bool] IsConnected() { if (![bool]("System.Net.NetworkInformation.Ping" -as 'type')) { Add-Type -AssemblyName System.Net.NetworkInformation }; $cs = $null; $re = @{ true = @{ m = "Success"; c = "Green" }; false = @{ m = "Failed"; c = "Red" } } Write-Host "[Github] Testing Connection ... " -ForegroundColor Blue -NoNewline try { [System.Net.NetworkInformation.PingReply]$PingReply = [System.Net.NetworkInformation.Ping]::new().Send("github.com"); $cs = $PingReply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success } catch [System.Net.Sockets.SocketException], [System.Net.NetworkInformation.PingException] { $cs = $false } catch { $cs = $false; Write-Error $_ } $re = $re[$cs.ToString()] Write-Host $re.m -ForegroundColor $re.c return $cs } } class GistFile { [string]$Name # with extention [string]$language [string]$type [string]$Owner [string]$raw_url [bool]$IsPublic [bool]$truncated [string]$Id [int]$size [GistFile[]]$files hidden [string]$content static [string]$UserName static [PsObject]$ChildItems GistFile([string]$filename) { $this.Name = $filename } GistFile([PsObject]$GistInfo) { $this.language = $GistInfo.language $this.IsPublic = $GistInfo.IsPublic $this.raw_url = $GistInfo.raw_url $this.type = $GistInfo.type $this.Name = $GistInfo.filename $this.size = $GistInfo.size $this.Id = $GistInfo.Id $this.Owner = $GistInfo.Owner if ([string]::IsNullOrWhiteSpace($this.Owner)) { if (![string]::IsNullOrWhiteSpace([GistFile]::UserName)) { $this.Owner = [GistFile]::UserName } else { Write-Warning "Gist Owner was not set!" } } if ($null -eq ([GistFile]::ChildItems) -and ![string]::IsNullOrWhiteSpace($this.Id)) { [GistFile]::ChildItems = [GitHub]::GetGist($this.Owner, $this.Id).files } if ($null -ne [GistFile]::ChildItems) { $_files = $null; [string[]]$filenames = [GistFile]::ChildItems | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name try { $_files = [GistFile[]]$filenames.Foreach({ $_Item = [GistFile]::ChildItems."$_" $_Gist = [GistFile]::new($_Item.filename) $_Gist.language = $_Item.language $_Gist.Ispublic = $this.IsPublic $_Gist.raw_url = $_Item.raw_url $_Gist.type = $_Item.type $_Gist.size = $_Item.size $_Gist.content = $_Item.content $_Gist.Owner = $this.Owner; $_Gist.Id = $this.Id $_Gist } ) } finally { [GistFile]::ChildItems = $null $this.files = $_files if ([string]::IsNullOrWhiteSpace($this.Name)) { $this.Name = $filenames[0] } } } } static [GistFile] Create([uri]$GistUri) { $res = $null; $ogs = $GistUri.OriginalString $IsRawUri = $ogs.Contains('/raw/') -and $ogs.Contains('gist.githubusercontent.com') $seg = $GistUri.Segments $res = $(if ($IsRawUri) { $_name = $seg[-1] $rtri = 'https://gist.github.com/{0}{1}' -f $seg[1], $seg[2] $rtri = $rtri.Remove($rtri.Length - 1) $info = [GitHub]::GetGist([uri]::new($rtri)) $file = $info.files."$_name" [PsCustomObject]@{ language = $file.language IsPublic = $info.IsPublic raw_url = $file.raw_url Owner = $info.owner.login type = $file.type filename = $_name size = $file.size Id = $seg[2].Replace('/', '') } } else { # $info = [GitHub]::GetGist($GistUri) [PsCustomObject]@{ language = '' IsPublic = $null raw_url = '' Owner = $seg[1].Split('/')[0] type = '' filename = '' size = '' Id = $seg[-1] } } ) if (![string]::IsNullOrWhiteSpace($res.Owner)) { [GistFile]::UserName = $res.Owner } return [GistFile]::New($res) } [string] ShowFileInfo() { return "File: $($this.Name)" } } class Gist { [uri] $Uri [string] $Id [string] $Owner [string] $Description [bool] $IsPublic [GistFile[]] $Files = @() Gist() {} Gist([string]$Name) { $this.AddFile([GistFile]::new($Name)) } [psobject] Post() { $gisfiles = @() $this.Files.Foreach({ $gisfiles += @{ $_.Name = @{ content = $_.Content } } } ) $data = @{ files = $gisfiles description = $this.Description public = $this.IsPublic } | ConvertTo-Json Write-Verbose ($data | Out-String) Write-Verbose "[PROCESS] Posting to https://api.github.com/gists" $invokeParams = @{ Method = 'Post' Uri = "https://api.github.com/gists" WebSession = [GitHub]::webSession Body = $data ContentType = 'application/json' } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $r = Invoke-RestMethod @invokeParams $r = $r | Select-Object @{Name = "Url"; Expression = { $_.html_url } }, Description, Public, @{Name = "Created"; Expression = { $_.created_at -as [datetime] } } return $r } [void] AddFile([GistFile]$file) { $this.Files += $file } [string] ShowInfo() { $info = "Gist ID: $($this.Id)" $info += "`nDescription: $($this.Description)" $info += "`nFiles:" foreach ($file in $this.Files.Values) { $info += "`n - $($file.ShowFileInfo())" } return $info } } #endregion GitHub class SignatureUtils { # Static Properties static [string] $SIGNATURE_KEYNAME = "signature" static [string] $AppName = "ASP" static [string] $NewLine = "`n" static [string] $EmptyUriPath = "/" static [string] $equals = "=" static [string] $And = "&" static [string] $UTF_8_Encoding = "UTF-8" # Static Methods # Method to sign parameters static [string] signParameters([hashtable] $parameters, [string] $key, [string] $HttpMethod, [string]$h0st, [string] $RequestURI, [string] $algorithm) { $stringToSign = [SignatureUtils]::calculateStringToSignV2($parameters, $HttpMethod, $h0st, $RequestURI) return [SignatureUtils]::sign($stringToSign, $key, $algorithm) } # Method to calculate the string to sign for SignatureVersion 2 static [string] calculateStringToSignV2([hashtable] $parameters, [string] $httpMethod, [string] $hostHeader, [string] $requestURI) { if (!$httpMethod) { throw "HttpMethod cannot be null" } $stringToSign = "$httpMethod$([SignatureUtils]::NewLine)" # Host header to lowercase $stringToSign += ($hostHeader.ToLower() + [SignatureUtils]::NewLine) # URI or fallback to empty path if (!$requestURI) { $stringToSign += [SignatureUtils]::EmptyUriPath } else { $stringToSign += [SignatureUtils]::UrlEncode($requestURI, $true) } $stringToSign += [SignatureUtils]::NewLine # Sort and encode parameters $sortedParamMap = [System.Collections.SortedList]::new($parameters, [System.StringComparer]::Ordinal) foreach ($key in $sortedParamMap.Keys) { if ($key -ieq [SignatureUtils]::SIGNATURE_KEYNAME) { continue } $stringToSign += [SignatureUtils]::UrlEncode($key, $false) + [SignatureUtils]::equals + [SignatureUtils]::UrlEncode($sortedParamMap[$key], $false) + [SignatureUtils]::And } return $stringToSign.Substring(0, $stringToSign.Length - 1) } # URL encode method static [string] UrlEncode([string] $data, [bool] $path) { $encoded = [string]::Empty $unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" + ($path ? "/" : "") $bytes = [System.Text.Encoding]::UTF8.GetBytes($data) foreach ($symbol in $bytes) { $char = [char]$symbol if ($unreservedChars.Contains($char)) { $encoded += $char } else { $encoded += "%" + "{0:X2}" -f $symbol } } return $encoded } # Method to compute the RFC 2104-compliant HMAC signature static [string] sign([string] $data, [string] $key, [string] $signatureMethod) { try { $encoding = [System.Text.ASCIIEncoding]::new() $hmac = [System.Security.Cryptography.HMAC]::Create($signatureMethod) $hmac.Key = $encoding.GetBytes($key) $hmac.Initialize() $dataBytes = $encoding.GetBytes($data) $rawResult = $hmac.ComputeHash($dataBytes) return [Convert]::ToBase64String($rawResult) } catch { throw "Failed to generate signature: $($_.Exception.Message)" } } } class FileMonitor { static [bool] $FileClosed = $true static [bool] $FileLocked = $false static [System.ConsoleKeyInfo[]] $Keys = @() static [ValidateNotNull()][IO.FileInfo] $FileTowatch static [ValidateNotNull()][string] $LogvariableName = $(if ([string]::IsNullOrWhiteSpace([FileMonitor]::LogvariableName)) { $n = ('fileMonitor_log_' + [guid]::NewGuid().Guid).Replace('-', '_'); Set-Variable -Name $n -Scope Global -Value ([string[]]@()); $n } else { [FileMonitor]::LogvariableName } ) static [System.IO.FileSystemWatcher] MonitorFile([string]$File) { return [FileMonitor]::monitorFile($File, { Write-Host "[+] File monitor Completed" -ForegroundColor Green }) } static [System.IO.FileSystemWatcher] MonitorFile([string]$File, [scriptblock]$Action) { [ValidateNotNull()][IO.FileInfo]$File = [IO.FileInfo][xcrypt]::GetUnResolvedPath($File) if (![IO.File]::Exists($File.FullName)) { throw "The file does not exist" } [FileMonitor]::FileTowatch = $File $watcher = [System.IO.FileSystemWatcher]::new(); $Watcher = New-Object IO.FileSystemWatcher ([IO.Path]::GetDirectoryName($File.FullName)), $File.Name -Property @{ IncludeSubdirectories = $false EnableRaisingEvents = $true } $watcher.Filter = $File.Name $watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite; $onChange = Register-ObjectEvent $Watcher Changed -Action { [FileMonitor]::FileLocked = $true } $OnClosed = Register-ObjectEvent $Watcher Disposed -Action { [FileMonitor]::FileClosed = $true } # [Console]::Write("Monitoring changes to $File"); [Console]::WriteLine("Press 'crl^q' to stop") do { try { [FileMonitor]::FileLocked = [FileMonitor]::IsFileLocked($File.FullName) } catch [System.IO.IOException] { [FileMonitor]::FileLocked = $(if ($_.Exception.Message.Contains('is being used by another process')) { $true } else { throw 'An error occured while checking the file' } ) } finally { [System.Threading.Thread]::Sleep(100) } } until ([FileMonitor]::FileClosed -and ![FileMonitor]::FileLocked -and ![FileMonitor]::IsFileOpenInVim($File.FullName)) Invoke-Command -ScriptBlock $Action Unregister-Event -SubscriptionId $onChange.Id; $onChange.Dispose(); Unregister-Event -SubscriptionId $OnClosed.Id; $OnClosed.Dispose(); $Watcher.Dispose(); return $watcher } static [PsObject] MonitorFileAsync([string]$filePath) { # .EXAMPLE # $flt = [FileMonitor]::MonitorFileAsync($filePath) # $flt.Thread.CloseInputStream(); # $flt.Thread.StopJobAsync(); # Stop-Job -Name $flt.Name -Verbose -PassThru | Remove-Job -Force -Verbose # $flt.Thread.Dispose()MOnitorFile # while ((Get-Job -Name $flt.Name).State -ne "Completed") { # # DO other STUFF here ... # } $threadscript = [scriptblock]::Create("[FileMonitor]::MonitorFile('$filePath')") $fLT_Name = "kLThread-$([guid]::NewGuid().Guid)" return [PSCustomObject]@{ Name = $fLT_Name Thread = Start-ThreadJob -ScriptBlock $threadscript -Name $fLT_Name } } static [string] GetLogSummary() { return [FileMonitor]::GetLogSummary([FileMonitor]::LogvariableName) } static [string] GetLogSummary([string]$LogvariableName) { [ValidateNotNullOrWhiteSpace()][string]$LogvariableName = $LogvariableName $l = Get-Variable -Name $LogvariableName -Scope Global -ValueOnly; $summ = ''; $rgx = "\[.*\] The file '.*' is open in nvim \(PID: \d+\)" if ($null -eq $l) { return '' }; $ct = $l.Where({ $_ -notmatch $rgx }) $LogSessions = @(); $LogSessions += $(if ($ct.count -gt 1) { (($l.ForEach({ if ($_ -notmatch $rgx) { $_ + '|' } else { $_ } })) -join "`n").Split('|') } else { [string]::Join("`n", $l) } ) foreach ($item in $LogSessions) { $s = ''; $lines = $item.Split("`n") 0 .. $lines.Count | ForEach-Object { if ($_ -eq 0) { $s += "$($lines[0])`n" } elseif ($lines[$_] -match $rgx -or $lines[$_ + 1] -match $rgx) { $s += '.' } else { $s += "`n$($lines[$_ - 1])" } } $summ += [string]::Join("`n", $s.Split("`n").ForEach({ if ($_ -like "......*") { 'â‹®' } else { $_ } })).Trim() $summ += "`n" } return $summ.Trim() } static [bool] IsFileOpenInVim([IO.FileInfo]$file) { $res = $null; $logvar = Get-Variable -Name ([FileMonitor]::LogvariableName) -Scope Global; $fileName = Split-Path -Path $File.FullName -Leaf; $res = $false; $_log_msg = @(); $processes = Get-Process -Name "nvim*", "vim*" -ErrorAction SilentlyContinue foreach ($process in $processes) { if ($process.CommandLine -like "*$fileName*") { $_log_msg = "[{0}] The file '{1}' is open in {2} (PID: {3})" -f [DateTime]::Now.ToString(), $fileName, $process.ProcessName, $process.Id $res = $true; continue } } $_log_msg = $_log_msg -join [Environment]::NewLine if ([string]::IsNullOrEmpty($_log_msg)) { $res = $false; $_log_msg = "[{0}] The file '{1}' is not open in vim" -f [DateTime]::Now.ToString(), $fileName } $logvar.Value += $_log_msg Set-Variable -Name ([FileMonitor]::LogvariableName) -Scope Global -Value $logvar.Value | Out-Null return $res } static [bool] IsFileLocked([string]$filePath) { $res = $true; $logvar = Get-Variable -Name ([FileMonitor]::LogvariableName) -Scope Global; $filePath = Resolve-Path -Path $filePath -ErrorAction SilentlyContinue try { # (lsof -t "$filePath" | wc -w) -gt 0 [System.IO.FileStream]$stream = [IO.File]::Open($filePath, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None) if ($stream) { $stream.Close(); $stream.Dispose() } $res = $false } finally { if ($res) { $logvar.Value += "[$([DateTime]::Now.ToString())] File is already locked by another process." } Set-Variable -Name ([FileMonitor]::LogvariableName) -Scope Global -Value $logvar.Value | Out-Null } return $res } } class SecretStore { [string]$Name [uri]$Url static hidden [ValidateNotNullOrWhiteSpace()][string]$DataPath SecretStore([string]$Name) { $this.Name = $Name if ([string]::IsNullOrWhiteSpace([SecretStore]::DataPath)) { [SecretStore]::DataPath = [IO.Path]::Combine([xcrypt]::Get_dataPath('ArgonCage', 'Data'), 'secrets') } $this.psobject.Properties.Add([psscriptproperty]::new('File', { return [IO.FileInfo]::new([IO.Path]::Combine([SecretStore]::DataPath, $this.Name)) }, { param($value) if ($value -is [IO.FileInfo]) { [SecretStore]::DataPath = $value.Directory.FullName $this.Name = $value.Name } else { throw "Invalid value assigned to File property" } } ) ) $this.psobject.Properties.Add([psscriptproperty]::new('Size', { if ([IO.File]::Exists($this.File.FullName)) { $this.File = Get-Item $this.File.FullName return $this.File.Length } return 0 }, { throw "Cannot set Size property" } ) ) } } #region FipsHMACSHA256 # .SYNOPSIS # A PowerShell class to provide a FIPS compliant alternative to the built-in [System.Security.Cryptography.HMACSHA256] # .DESCRIPTION # FIPS (Federal Information Processing Standard) is a set of guidelines that specify the security requirements for cryptographic algorithms and protocols used in the United States government. # A FIPS compliant algorithm is one that has been reviewed and approved by the National Institute of Standards and Technology (NIST) to meet certain security standards. # The HMAC is a type of message authentication code that uses a secret key to verify the authenticity and integrity of a message. # It is based on a hash function, such as SHA-256, which is a cryptographic function that produces a fixed-size output (called a hash or message digest) from a variable-size input. # The built-in HMACSHA256 class in .NET Framework implements the HMAC using the SHA-256 hash function. # However, in older versions the HMACSHA256 class may not be FIPS compliant. # .EXAMPLE # $br = [System.Text.Encoding]::UTF8.GetBytes("Hello world!") # $hc = [FipsHmacSha256]::new() # $hc.ComputeHash($br) class FipsHmacSha256 : System.Security.Cryptography.HMAC { static hidden $rng static [System.Security.Cryptography.HMACSHA256] $HMAC static [ValidateNotNullOrEmpty()] [byte[]] $key FipsHmacSha256() { $this._Init(); } FipsHmacSha256([Byte[]]$key) { [FipsHmacSha256]::Key = $key; $this._Init(); } [string] ComputeHash([byte[]] $data) { if ($null -eq [FipsHmacSha256]::HMAC) { [FipsHmacSha256]::HMAC = [System.Security.Cryptography.HMACSHA256]::new([FipsHmacSha256]::key) } $hashBytes = [FipsHmacSha256]::HMAC.ComputeHash($data) $hash = [BitConverter]::ToString($hashBytes) -replace '-' return $hash } hidden [void] _Init() { if ($null -eq [FipsHmacSha256].RNG) { [FipsHmacSha256].psobject.Properties.Add([psscriptproperty]::new('RNG', { return [System.Security.Cryptography.RNGCryptoServiceProvider]::new() } ) ) } $flags = [Reflection.BindingFlags]'Instance, NonPublic' [Reflection.FieldInfo]$m_hashName = [System.Security.Cryptography.HMAC].GetField('m_hashName', $flags) [Reflection.FieldInfo]$m_hash1 = [System.Security.Cryptography.HMAC].GetField('m_hash1', $flags) [Reflection.FieldInfo]$m_hash2 = [System.Security.Cryptography.HMAC].GetField('m_hash2', $flags) if ($null -ne $m_hashName) { $m_hashName.SetValue($this, 'SHA256') } if ($null -ne $m_hash1) { $m_hash1.SetValue($this, [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()) } if ($null -ne $m_hash2) { $m_hash2.SetValue($this, [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()) } if ($null -eq [FipsHmacSha256]::key) { $randomBytes = [Byte[]]::new(64); [FipsHmacSha256].RNG.GetBytes($randomBytes) [FipsHmacSha256]::Key = $randomBytes # Write-Verbose "Hexkey = $([BitConverter]::ToString([FipsHmacSha256]::Key).Tolower() -replace '-')" -verbose } $this.HashSizeValue = 256 } } #endregion FipsHMACSHA256 #region OTPKIT class OTPKIT { [string] $key = "" static [string] CreateHOTP([string]$SECRET, [int]$Phone) { return [OTPKIT]::CreateHOTP($phone, [OTPKIT]::GetOtp($SECRET)) } static [string] CreateHOTP([int]$phone, [string]$otp) { return [OTPKIT]::CreateHOTP($phone, $otp, 5) } static [string] CreateHOTP([int]$phone, [string]$otp, [int]$expiresAfter) { $ttl = $expiresAfter * 60 * 1000 $expires = (Get-Date).AddMilliseconds($ttl).ToString("yyyy-MM-ddTHH:mm:ss.fffZ") $bytes = [byte[]]::new(16) [System.Security.Cryptography.RNGCryptoServiceProvider]::new().GetBytes($bytes) $salt = [BitConverter]::ToString($bytes) -replace '-' $data = "$phone.$otp.$expires.$salt" Write-Host "data: " -NoNewline -ForegroundColor Green; Write-Host "$data" -ForegroundColor Blue $hashBase = [FipsHmacSha256]::new().ComputeHash([System.Text.Encoding]::ASCII.GetBytes($data)) $hash = "$hashBase.$expires.$salt" # Import the Twilio module Import-Module -Name Twilio $phoneNumber = "+1234567890" $message = "Hello, this is a test message." [OTPKIT]::Send_TWILIO_SMS($phoneNumber, $message) return $hash } static [bool] Send_TWILIO_SMS ([string]$PhoneNumber, [string]$Message) { # Function to send SMS using Twilio # Twilio account SID and auth token $accountSid = "YOUR_TWILIO_ACCOUNT_SID" $authToken = "YOUR_TWILIO_AUTH_TOKEN" # Twilio phone number $twilioPhoneNumber = "YOUR_TWILIO_PHONE_NUMBER" # Create a new Twilio client $twilio = New-TwilioRestClient -AccountSid $accountSid -AuthToken $authToken # Send the SMS message $twilio.SendMessage($twilioPhoneNumber, $PhoneNumber, $Message) return $? } static [bool] VerifyHOTP([string]$otp, [int]$phone, [string]$hash) { if (!$hash -match "\.") { return $false } $hashValue, $expires, $salt = $hash -split "\." $now = (Get-Date).Ticks / 10000 if ($now -gt [double]$expires) { return $false } $data = "$phone.$otp.$expires.$salt" Write-Host "data: " -NoNewline -ForegroundColor Green; Write-Host "$data" -ForegroundColor Blue $newCalculatedHash = [FipsHmacSha256]::new().ComputeHash([System.Text.Encoding]::ASCII.GetBytes($data)) if ($newCalculatedHash -eq $hashValue) { return $true } return $false } static [string] ParseOtpUrl([string]$otpURL) { # $otpURL can be decrypted text if (![System.Uri]::IsWellFormedUriString("$otpURL", "Absolute") -or $otpURL -notmatch "^otpauth://") { Write-Host "The decrypted text is not a valid OTP URL" "Error" ; $script:FileBrowser.Dispose() ; exit 1 } $parseOtpUrl = [scriptblock]::Create("[System.Web.HttpUtility]::ParseQueryString(([uri]::new('$otpURL')).Query)").Invoke() $otpType = $([uri]$otpURL).Host if ($otpType -eq "hotp") { Write-Warning "TOTP is only supported" } if ($otpType -eq "totp" ) { $otpType = "$otpType=" } $otpPeriod = if (![string]::IsNullOrEmpty($parseOtpUrl["period"])) { $parseOtpUrl["period"] } else { 30 } $otpDigits = if (![string]::IsNullOrEmpty($parseOtpUrl["digits"])) { $parseOtpUrl["digits"] } else { 6 } $otpSecret = $parseOtpUrl["secret"] return [OTPKIT]::GetOtp($otpSecret, $otpDigits, $otpPeriod) } static [string] GetOtp([string]$SECRET) { return [OTPKIT]::GetOtp($SECRET, 4, "5") } static [string] GetOtp([string]$SECRET, [int]$LENGTH, [string]$WINDOW) { $hmac = New-Object -TypeName System.Security.Cryptography.HMACSHA1 $hmac.key = $([OTPKIT]::ConvertBase32ToHex($SECRET.ToUpper())) -replace '^0x', '' -split "(?<=\G\w{2})(?=\w{2})" | ForEach-Object { [Convert]::ToByte( $_, 16 ) } $timeSpan = $(New-TimeSpan -Start (Get-Date -Year 1970 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0) -End (Get-Date).ToUniversalTime()).TotalSeconds $rndHash = $hmac.ComputeHash([byte[]][BitConverter]::GetBytes([Convert]::ToInt64([Math]::Floor($timeSpan / $WINDOW)))) $toffset = $rndhash[($rndHash.Length - 1)] -band 0xf $fullOTP = ($rndhash[$toffset] -band 0x7f) * [math]::pow(2, 24) $fullOTP += ($rndHash[$toffset + 1] -band 0xff) * [math]::pow(2, 16) $fullOTP += ($rndHash[$toffset + 2] -band 0xff) * [math]::pow(2, 8) $fullOTP += ($rndHash[$toffset + 3] -band 0xff) $modNumber = [math]::pow(10, $LENGTH) $otp = $fullOTP % $modNumber $otp = $otp.ToString("0" * $LENGTH) return $otp } static [string] ConvertBase32ToHex([string]$base32) { $base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; $bits = ""; $hex = ""; for ($i = 0; $i -lt $base32.Length; $i++) { $val = $base32chars.IndexOf($base32.Chars($i)); $binary = [Convert]::ToString($val, 2) $str = $binary.ToString(); $len = 5 $pad = '0' if (($len + 1) -ge $str.Length) { while (($len - 1) -ge $str.Length) { $str = ($pad + $str) } } $bits += $str } for ($i = 0; $i + 4 -le $bits.Length; $i += 4) { $chunk = $bits.Substring($i, 4) # Write-Host $chunk $intChunk = [Convert]::ToInt32($chunk, 2) $hexChunk = '{0:x}' -f $([int]$intChunk) # Write-Host $hexChunk $hex = $hex + $hexChunk } return $hex; } } #endregion OTPKIT #region VaultStuff # A managed credential object. Makes it easy to protect, convert, save and stuff .. class CredManaged { [string]$target [CredType]hidden $type = [CredType]1; [bool]hidden $IsProtected = $false; [ValidateNotNullOrEmpty()][string]$UserName = $(whoami); [ValidateNotNullOrEmpty()][securestring]$Password = [securestring]::new(); [ValidateNotNullOrEmpty()][string]hidden $Domain = [System.Environment]::UserDomainName; [ValidateSet('User', 'Machine')][ValidateNotNullOrEmpty()][string]hidden $Scope = 'User'; CredManaged() {} CredManaged([string]$target, [string]$username, [SecureString]$password) { ($this.target, $this.username, $this.password) = ($target, $username, $password) } CredManaged([string]$target, [string]$username, [SecureString]$password, [CredType]$type) { ($this.target, $this.username, $this.password, $this.type) = ($target, $username, $password, $type) } CredManaged([PSCredential]$PSCredential) { ($this.UserName, $this.Password) = ($PSCredential.UserName, $PSCredential.Password) } CredManaged([string]$target, [PSCredential]$PSCredential) { ($this.target, $this.UserName, $this.Password) = ($target, $PSCredential.UserName, $PSCredential.Password) } [void] Protect() { $_scope_ = [EncryptionScope]$this.Scope $_Props_ = @($this | Get-Member -Force | Where-Object { $_.MemberType -eq 'Property' -and $_.Name -ne 'Scope' } | Select-Object -ExpandProperty Name) foreach ($n in $_Props_) { $OBJ = $this.$n if ($n.Equals('Password')) { $this.$n = $(Protect-Data -MSG ($OBJ | xconvert ToString) -Scope $_scope_) | xconvert ToBase85, ToSecurestring } else { $this.$n = Protect-Data -MSG $OBJ -Scope $_scope_ } } Invoke-Command -InputObject $this.IsProtected -NoNewScope -ScriptBlock $([ScriptBlock]::Create({ $this.psobject.Properties.Add([psscriptproperty]::new('IsProtected', { return $true })) } ) ) } [void] UnProtect() { $_scope_ = [EncryptionScope]$this.Scope $_Props_ = @($this | Get-Member -Force | Where-Object { $_.MemberType -eq 'Property' -and $_.Name -ne 'Scope' } | Select-Object -ExpandProperty Name) foreach ($n in $_Props_) { $OBJ = $this.$n if ($n.Equals('Password')) { $this.$n = UnProtect-Data -MSG ($OBJ | xconvert ToString, FromBase85, ToString) -Scope $_scope_ | xconvert ToSecurestring; } else { $this.$n = UnProtect-Data -MSG $OBJ -Scope $_scope_; } } Invoke-Command -InputObject $this.IsProtected -NoNewScope -ScriptBlock $([ScriptBlock]::Create({ $this.psobject.Properties.Add([psscriptproperty]::new('IsProtected', { return $false })) } ) ) } [void] SaveToVault() { $CredMan = [CredentialManager]::new(); [void]$CredMan.SaveCredential($this.target, $this.UserName, $this.Password); } [string]ToString() { $str = $this.UserName if ($str.Length -gt 9) { $str = $str.Substring(0, 6) + '...' } return $str } } class NativeCredential { [System.Int32]$AttributeCount [UInt32]$CredentialBlobSize [IntPtr]$CredentialBlob [IntPtr]$TargetAlias [System.Int32]$Type [IntPtr]$TargetName [IntPtr]$Attributes [IntPtr]$UserName [UInt32]$Persist [IntPtr]$Comment NativeCredential([CredManaged]$Cr3dential) { $this._init_(); $this.CredentialBlobSize = [UInt32](($Cr3dential.password.Length + 1) * 2) $this.TargetName = [System.Runtime.InteropServices.Marshal]::StringToCoTaskMemUni($Cr3dential.target) $this.CredentialBlob = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($Cr3dential.password) $this.UserName = [System.Runtime.InteropServices.Marshal]::StringToCoTaskMemUni($Cr3dential.username) } NativeCredential([string]$target, [string]$username, [securestring]$password) { $this._init_(); $this.CredentialBlobSize = [UInt32](($password.Length + 1) * 2); $this.TargetName = [System.Runtime.InteropServices.Marshal]::StringToCoTaskMemUni($target); $this.CredentialBlob = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($password); $this.UserName = [System.Runtime.InteropServices.Marshal]::StringToCoTaskMemUni($username); } hidden _init_() { $this.AttributeCount = 0 $this.Comment = [IntPtr]::Zero $this.Attributes = [IntPtr]::Zero $this.TargetAlias = [IntPtr]::Zero $this.Type = [CredType]::Generic.value__ $this.Persist = [UInt32] [CredentialPersistence]::LocalComputer } } # Windows credential manager class CredentialManager { static $LastErrorCode CredentialManager() { $this::Init() } [object] static hidden Advapi32() { return (New-Object -TypeName CredentialManager.Advapi32) } static [void] SaveCredential([string]$title, [SecureString]$SecureString) { $UserName = [System.Environment]::GetEnvironmentVariable('UserName'); [CredentialManager]::SaveCredential([CredManaged]::new($title, $UserName, $SecureString)); } static [void] SaveCredential([string]$title, [string]$UserName, [SecureString]$SecureString) { [CredentialManager]::SaveCredential([CredManaged]::new($title, $UserName, $SecureString)); } static [void] SaveCredential([CredManaged]$Object) { if ($null -eq [CredentialManager].CONSTANTS) { [CredentialManager]::Init() } # Create the native credential object. $NativeCredential = New-Object -TypeName CredentialManager.Advapi32+NativeCredential; foreach ($prop in ([NativeCredential]::new($Object).PsObject.properties)) { $NativeCredential."$($prop.Name)" = $prop.Value } # Save Generic credential to the Windows Credential Vault. $result = [CredentialManager]::Advapi32()::CredWrite([ref]$NativeCredential, 0) [CredentialManager]::LastErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); if (!$result) { throw [Exception]::new("Error saving credential: 0x" + "{0}" -f [CredentialManager]::LastErrorCode) } # Clean up memory allocated for the native credential object. [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($NativeCredential.TargetName) [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($NativeCredential.CredentialBlob) [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($NativeCredential.UserName) } static [bool] Remove([string]$target, [CredType]$type) { if ($null -eq [CredentialManager].CONSTANTS) { [CredentialManager]::Init() } $Isdeleted = [CredentialManager]::Advapi32()::CredDelete($target, $type, 0); [CredentialManager]::LastErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); if (!$Isdeleted) { if ([CredentialManager]::LastErrorCode -eq [CredentialManager].CONSTANTS.ERROR_NOT_FOUND) { throw [CredentialNotFoundException]::new("DeleteCred failed with the error code $([CredentialManager]::LastErrorCode) (credential not found)."); } else { throw [Exception]::new("DeleteCred failed with the error code $([CredentialManager]::LastErrorCode)."); } } return $Isdeleted } [CredManaged] static GetCredential([string]$target) { #uses the default $(whoami) return [CredentialManager]::GetCredential($target, (Get-Item Env:\USERNAME).Value); } [CredManaged] static GetCredential([string]$target, [string]$username) { return [CredentialManager]::GetCredential($target, [CredType]::Generic, $username); } # Method for retrieving a saved credential from the Windows Credential Vault. [CredManaged] static GetCredential([string]$target, [CredType]$type, [string]$username) { if ($null -eq [CredentialManager].CONSTANTS) { [CredentialManager]::Init() } $NativeCredential = New-Object -TypeName CredentialManager.Advapi32+NativeCredential; foreach ($prop in ([NativeCredential]::new($target, $username, [securestring]::new()).PsObject.properties)) { $NativeCredential."$($prop.Name)" = $prop.Value } # Declare variables $AdvAPI32 = [CredentialManager]::Advapi32() $outCredential = [IntPtr]::Zero # To hold the retrieved native credential object. # Try to retrieve the credential from the Windows Credential Vault. $result = $AdvAPI32::CredRead($target, $type.value__, 0, [ref]$outCredential) [CredentialManager]::LastErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); if (!$result) { $errorCode = [CredentialManager]::LastErrorCode if ($errorCode -eq [CredentialManager].CONSTANTS.ERROR_NOT_FOUND) { $(Get-Variable host).value.UI.WriteErrorLine("`nERROR_NOT_FOUND: Credential '$target' not found in Windows Credential Vault. Returning Empty Object ...`n"); return [CredManaged]::new(); } else { throw [Exception]::new("Error reading '{0}' in Windows Credential Vault. ErrorCode: 0x{1}" -f $target, $errorCode) } } # Convert the retrieved native credential object to a managed Credential object & Get the Credential from the mem location $NativeCredential = [System.Runtime.InteropServices.Marshal]::PtrToStructure($outCredential, [Type]"CredentialManager.Advapi32+NativeCredential") -as 'CredentialManager.Advapi32+NativeCredential' [System.GC]::Collect(); $target = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($NativeCredential.TargetName) $password = [Runtime.InteropServices.Marshal]::PtrToStringUni($NativeCredential.CredentialBlob) $targetuser = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($NativeCredential.UserName) $credential = [CredManaged]::new($target, $targetuser, ($password | xconvert ToSecurestring)); # Clean up memory allocated for the native credential object. [void]$AdvAPI32::CredFree($outCredential); [CredentialManager]::LastErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); # Return the managed Credential object. return $credential } [System.Collections.ObjectModel.Collection[CredManaged]] static RetreiveAll() { $Credentials = [System.Collections.ObjectModel.Collection[CredManaged]]::new(); # CredEnumerate is slow af so, I ditched it. $credList = [CredentialManager]::get_StoredCreds(); foreach ($cred in $credList) { Write-Verbose "CredentialManager.GetCredential($($cred.Target))"; $Credentials.Add([CredManaged]([CredentialManager]::GetCredential($cred.Target, $cred.Type, $cred.User))); } return $Credentials } [Psobject[]] static hidden get_StoredCreds() { $_Host_OS = [GitHub]::Get_Host_Os() if ($_Host_OS -in ('Linux', 'MacOs')) { throw [System.Exception]::new('UnsupportedPlatform: get_StoredCreds() works on Windows Only.') } # until I know the existance of a [wrapper module](https://learn.microsoft.com/en-us/powershell/utility-modules/crescendo/overview?view=ps-modules), I'll stick to this Hack. $cmdkey = (Get-Command cmdkey -ErrorAction SilentlyContinue).Source if ([string]::IsNullOrEmpty($cmdkey)) { throw [System.Exception]::new('get_StoredCreds() Failed.') } $outputLines = (&$cmdkey /list) -split "`n" [CredentialManager]::LastErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); if ($outputLines) { } else { throw $error[0].Exception.Message } $target = $type = $user = $perst = $null $credList = $(foreach ($line in $outputLines) { if ($line -match "^\s*Target:\s*(.+)$") { $target = $matches[1] } elseif ($line -match "^\s*Type:\s*(.+)$") { $type = $matches[1] } elseif ($line -match "^\s*User:\s*(.+)$") { $user = $matches[1] } elseif ($line -match "^\s*Local machine persistence$") { $perst = "LocalComputer" } elseif ($line -match "^\s*Enterprise persistence$") { $perst = 'Enterprise' } if ($target -and $type -and $user -and ![string]::IsNullOrEmpty($perst)) { [PSCustomObject]@{ Target = [string]$target Type = [CredType]$type User = [string]$user Persistence = [CredentialPersistence]$perst } $target = $type = $user = $perst = $null } } ) | Select-Object @{l = 'Target'; e = { $_.target.replace('LegacyGeneric:target=', '').replace('WindowsLive:target=', '') } }, Type, User, Persistence | Where-Object { $_.target -ne 'virtualapp/didlogical' }; return $credList } static hidden [void] Init() { $Host_OS = $(if ($(Get-Variable PSVersionTable -Value).PSVersion.Major -le 5 -or $(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }); if ($Host_OS -ne "Windows") { throw "Error: '$Host_OS' is Unsupported. CredentialManager class works on windows only." } $CONSTANTS = [psobject]::new() $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('ERROR_SUCCESS', { return 0 })) $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('ERROR_NOT_FOUND', { return 1168 })) $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('ERROR_INVALID_FLAGS', { return 1004 })) $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('CRED_PERSIST_LOCAL_MACHINE', { return 2 })) $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('CRED_MAX_USERNAME_LENGTH', { return 514 })) $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('CRED_MAX_CREDENTIAL_BLOB_SIZE', { return 512 })) $CONSTANTS.psobject.Properties.Add([psscriptproperty]::new('CRED_MAX_GENERIC_TARGET_LENGTH', { return 32767 })) [CredentialManager].psobject.Properties.Add([psscriptproperty]::new('CONSTANTS', { return $CONSTANTS })) # Import native functions from Advapi32.dll # No other choice but to use Add-Type. ie: https://stackoverflow.com/questions/64405866/invoke-runtime-interopservices-dllimportattribute # So CredentialManager.Advapi32+functionName it is! if (![bool]('CredentialManager.Advapi32' -as 'type')) { Add-Type -Namespace CredentialManager -Name Advapi32 -MemberDefinition $(Expand-Data -str 'H4sIAAAAAAAEALVUS2/aQBC+91eMOIFqWQT6kIo4UB4VKkQohuYQ5bDYA1lpvUt31zSk6n/vrB+AMW1a2vhge+f5ffONDQCwSZaCh4AyiaGvMZrvNggfIOHSwvdXkF+fUKKmsC5ceTBQMeNyxoz5pnREtlZh66O2fMVDZpHM7cL8hRu+FHiU8cYrSpZT3hYpw0eLMkIX+86DKXvkMQHswvv9YfhIx3rheQ1XzWazkQL+kd5Pic1QG25slVuAxnAlM24TFTIxZeEDl5gxG0qLeqO5SSkdNbgLrE5CO2E7ldh69vjMZeQH+DVBaTkTHvQfmA7QUmr+5i8kD1WEjftjlCYtleLMMg/w8ogU9EiwtekUpr1c7tY5KsXlGuZMr9Fes7ji6as4piZ784BGP+cxwoQZe6u5pcl3Sm1JOKdbwJ8qxQpN9/ZgZyzGNIMwoVK77AWDLDo7VHKO5cmfZQA9S/nLxGJfJUfIx9LOrD54zfkh9ARnFdfCoE6n87KKXjPLt3jQFS4XNmd7RtjccypsLsUNjYzk9cdukdUmQL3lIRqfwl1944/Gk+F8PB3+egEO+D8KtSztQdG7FHGyPv9F0hL9sqS566ykAyHG8UZpW6/1oi3b8HbLj4SopR+23s2UA9OFmiNwgyy6rf1GYo822LopDbVWmvwkMul+0JzUpl8O/bu0hKVSAoqy9buxvC92z6YcPEhte7Et3XKbw6SR6GwxcqvhAW1iQTPcj5pOjc7f03QS4wvwTOtmRDWuqqufEKHDMae6IFbtFqzcB3AJmZFGrF2G16VmcKuTdS3wD6Z7pu85lAMU+MzMn4Wb1fi3RWp0fgKYas6b9AcAAA==') } if (Get-Service vaultsvc -ErrorAction SilentlyContinue) { Start-Service vaultsvc -ErrorAction Stop } } } # .DESCRIPTION # Hashicorp VaultClient helper class for interacting with a Vault server by the Vault API. # To use this class, you will need to have access to a running instance of Hashicorp Vault. # You can download it from from the Hashicorp website at https://www.hashicorp.com/products/vault/. # .EXAMPLE # $vaucl = [VaultClient]::new('<VAULT_ADDRESS>', '<VAULT_TOKEN>', '<VAULT_PROTOCOL>') # $vault = $vaucl.GetVaultServer() # $secrets = $vaucl.GetVaultSecretList('<VAULT_PATH>') # $vaucl.SetVaultSecret('<VAULT_PATH>', @{key = 'value'}) class VaultClient { # Properties [string] $Address # This is the URL or IP address of your Vault server, including the port number if applicable. You can find this value in the api_addr field of the vault.hcl configuration file, or you can use the vault status command to display the current address of the Vault server. [string] $Token # This is a token that is used to authenticate with the Vault server. You can generate a new token using the vault token create command, or you can use the vault status command to display the current token of the Vault server. [string] $Protocol # This is the protocol that will be used to communicate with the Vault server. The most common value is https, but you can also use http if you have configured your Vault server to use an insecure connection. You can find this value in the api_addr field of the vault.hcl configuration file, or you can use the vault status command to display the current protocol of the Vault server. [string] $Url = [string]::Empty hidden [PSCustomObject] $ClientObj = $null static hidden $releases # ie: [Microsoft.PowerShell.Commands.HtmlWebResponseObject] # Constructor for the VaultClient class VaultClient([string]$address, [string]$token, [string]$protocol) { $this.Address = $address $this.Token = $token $this.Protocol = $protocol # Set the Vault URL $this.Url = "{0}://{1}/v1/" -f $protocol, $address # Generate the vaultClient object $this.GenerateVaultClient($token) } # Download and Install the latest Vault static [void] Install() { [console]::Write("Check Latest Version ...") [VaultClient]::releases = Invoke-WebRequest "https://releases.hashicorp.com/vault/" -Verbose:$false [string]$latestver = $([VaultClient]::releases.Links | ForEach-Object { $_.outerText.split('-')[0].split('_')[1] -as 'version' } | Sort-Object -Descending)[0].tostring() [VaultClient]::Install($latestver) } static [void] Install([string]$version) { [console]::WriteLine(" Found vault version: $version") if ($null -eq [VaultClient]::releases) { [VaultClient]::releases = Invoke-WebRequest "https://releases.hashicorp.com/vault/" -Verbose:$false } [string]$latest_dl = $(Invoke-WebRequest ("https://releases.hashicorp.com" + ([VaultClient]::releases.Links | Where-Object { $_.href -like "*/$version/*" } | Select-Object -ExpandProperty href)) -Verbose:$false).Links.href | Where-Object { $_ -like "*windows_386*" } $p = Get-Variable progressPreference -ValueOnly; $progressPreference = "SilentlyContinue" $Outfile = [IO.FileInfo]::new([xcrypt]::GetUnResolvedPath("vault_$version.zip")); try { $should_download = $true if ($Outfile.Exists()) { #TODO: Check version of the file, if the version is lower, then $should_download = $true } if ($should_download) { $Outfile = [scriptblock]::Create("[NetworkManager]::DownloadFile([uri]::new('$latest_dl'), '$($Outfile.FullName)')").Invoke() } if ($Outfile.Exists()) { Expand-Archive -Path $Outfile.FullName -DestinationPath "C:\Program Files\Vault\" } [void][System.Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\Program Files\Vault\", "Machine") } catch { throw $_ } finally { Remove-Item $Outfile -Force -ErrorAction SilentlyContinue $progressPreference = $p } # refresh env: like Update-SessionEnvironment from choco~ } # Method to generate the vaultClient object [void] GenerateVaultClient([string]$token) { # Set the Vault token $this.ClientObj = @{ Token = $token Headers = @{ 'X-Vault-Token' = $token } BaseUri = $this.Url } } # Method to get a Vault server [PSCustomObject] GetVaultServer() { # Create a custom object with the vaultClient object $vault = New-Object -TypeName PSObject -Property $this.ClientObj # Return the custom object return $vault } # Method to get a Vault secret [Hashtable] GetVaultSecret([string]$path) { # Get the server $vault = $this.GetVaultServer() # Get the secret at the specified path $uri = $vault.BaseUri + $path $secret = Invoke-WebRequest -Uri $uri -Headers $vault.Headers | Select-Object -ExpandProperty Content | ConvertFrom-Json | Select-Object -ExpandProperty data # Return the secret return $secret } # Method to get a list of Vault secrets [string[]] GetVaultSecretList([string]$path) { return $this.GetVaultSecretList($path, $null) } # Method to get a list of Vault secrets [string[]] GetVaultSecretList([string]$path, [PSCustomObject]$vault = $null) { # Get the server if (!$vault) { $vault = $this.GetVaultServer() } # Get the list of secrets $uri = $vault.BaseUri + $path $secrets = Invoke-WebRequest -Uri $uri -Headers $vault.Headers -CustomMethod 'list' | Select-Object -ExpandProperty Content | ConvertFrom-Json | Select-Object -ExpandProperty data | Select-Object -ExpandProperty keys | ForEach-Object { if ($_ -like '*/') { $this.GetVaultSecretList($("$path/$_").Trim("/"), $vault) } else { "$path/$_" } } # Return the list of secrets return $secrets } # Method to set a Vault secret [void] SetVaultSecret([string]$path, [Hashtable]$secret) { $vault = $this.GetVaultServer() # Set the secret at the specified path $uri = $vault.BaseUri + $path $data = @{data = $secret } | ConvertTo-Json Invoke-WebRequest -Uri $uri -Headers $vault.Headers -Method 'POST' -Body $data | Select-Object -ExpandProperty Content | ConvertFrom-Json | Select-Object -ExpandProperty data } # Method to remove a Vault secret [void] RemoveVaultSecret([string]$path) { $vault = $this.GetVaultServer() # Remove the secret at the specified path $uri = $vault.BaseUri + $path Invoke-WebRequest -Uri $uri -Headers $vault.Headers -Method 'DELETE' } # Method to get a Vault group [Hashtable] GetVaultGroup([string]$name) { $vault = $this.GetVaultServer() # Get the group with the specified name $uri = $vault.BaseUri + "identity/group/name/$name" $group = Invoke-WebRequest -Uri $uri -Headers $vault.Headers | Select-Object -ExpandProperty Content | ConvertFrom-Json | Select-Object -ExpandProperty data # Return the group return $group } # Method to set a Vault group [void] SetVaultGroup([Hashtable]$group) { $vault = $this.GetVaultServer() # Set the group $uri = $vault.BaseUri + "identity/group" $data = $group | ConvertTo-Json Invoke-WebRequest -Uri $uri -Headers $vault.Headers -Method 'POST' -Body $data } # Method to remove a Vault group [void] RemoveVaultGroup([string]$name) { $vault = $this.GetVaultServer() # Remove the group with the specified name $uri = $vault.BaseUri + "identity/group/name/$name" Invoke-WebRequest -Uri $uri -Headers $vault.Headers -Method 'DELETE' } # Method to get a Vault policy [string] GetVaultPolicy([string]$name) { $vault = $this.GetVaultServer() # Get the policy with the specified name $uri = $vault.BaseUri + "sys/policy/$name" $policy = Invoke-WebRequest -Uri $uri -Headers $vault.Headers | Select-Object -ExpandProperty Content | ConvertFrom-Json | Select-Object -ExpandProperty data | Select-Object -ExpandProperty rules # Return the policy return $policy } # Method to set a Vault policy [void] SetVaultPolicy([string]$name, [string]$rules) { $vault = $this.GetVaultServer() # Set the policy with the specified name $uri = $vault.BaseUri + "sys/policy/$name" $data = @{rules = $rules } | ConvertTo-Json Invoke-WebRequest -Uri $uri -Headers $vault.Headers -Method 'POST' -Body $data } # Method to remove a Vault policy [void] RemoveVaultPolicy([string]$name) { $vault = $this.GetVaultServer() # Remove the policy with the specified name $uri = $vault.BaseUri + "sys/policy/$name" Invoke-WebRequest -Uri $uri -Headers $vault.Headers -Method 'DELETE' } # Method to get a list of Vault policies [string[]] GetVaultPolicyList() { $vault = $this.GetVaultServer() # Get the list of policies $uri = $vault.BaseUri + "sys/policy" $policies = Invoke-WebRequest -Uri $uri -Headers $vault.Headers | Select-Object -ExpandProperty Content | ConvertFrom-Json | Select-Object -ExpandProperty data | Select-Object -ExpandProperty keys # Return the list of policies return $policies } } #endregion vaultStuff #region securecodes~Expiration Class Expiration { [Datetime]$Date [Timespan]$TimeSpan [String]$TimeStamp [ExpType]$Type Expiration() { $this.TimeSpan = [Timespan]::FromMilliseconds([DateTime]::Now.Millisecond) $this.Date = [datetime]::Now + $this.TimeSpan $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([int]$Years) { # ($Months, $Years) = if ($Years -eq 1) { (12, 0) }else { (0, $Years) }; # $CrDate = [datetime]::Now; # $Months = [int]($CrDate.Month + $Months); if ($Months -gt 12) { $Months -= 12 }; $this.TimeSpan = [Timespan]::new((365 * $years), 0, 0, 0); $this.Date = [datetime]::Now + $this.TimeSpan $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([int]$Years, [int]$Months) { $this.TimeSpan = [Timespan]::new((365 * $years + $Months * 30), 0, 0, 0); $this.Date = [datetime]::Now + $this.TimeSpan $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([datetime]$date) { $this.Date = $date $this.TimeSpan = $date - [datetime]::Now; $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([string]$dateString) { $this.Date = $dateString | xconvert ToDateTime $this.TimeSpan = $this.Date - [datetime]::Now; $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([System.TimeSpan]$TimeSpan) { $this.TimeSpan = $TimeSpan; $this.Date = [datetime]::Now + $this.TimeSpan $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([int]$hours, [int]$minutes, [int]$seconds) { $this.TimeSpan = [Timespan]::new($hours, $minutes, $seconds); $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } Expiration([int]$days, [int]$hours, [int]$minutes, [int]$seconds) { $this.TimeSpan = [Timespan]::new($days, $hours, $minutes, $seconds) $this.Date = [datetime]::Now + $this.TimeSpan $this.setExpType($this.TimeSpan); $this.setTimeStamp($this.TimeSpan); } [void]setTimeStamp([System.TimeSpan]$TimeSpan) { if ($null -eq $this.Date) { $this.TimeStamp = [DateTime]::Now.Add([Timespan]::FromMilliseconds($TimeSpan.TotalMilliseconds)).ToString("yyyyMMddHHmmssffff"); } else { $this.TimeStamp = $this.Date.ToString("yyyyMMddHHmmssffff") } } [void]hidden setExpType([Timespan]$TimeSpan) { $this.Type = switch ($true) { $($TimeSpan.Days -ge 365) { [ExpType]::Years; break } $($TimeSpan.Days -ge 30) { [ExpType]::Months; break } $($TimeSpan.Days -ge 1) { [ExpType]::Days; break } $($TimeSpan.Hours -ge 1) { [ExpType]::Hours; break } $($TimeSpan.Minutes -ge 1) { [ExpType]::Minutes; break } $($TimeSpan.Seconds -ge 1) { [ExpType]::Seconds; break } Default { [ExpType]::Milliseconds; break } } } [int]GetDays () { return $this.TimeSpan.Days } [int]GetMonths () { return [int]($this.TimeSpan.Days / 30) } [int]GetYears () { return [int]($this.TimeSpan.Days / 365) } [string]ToString() { if ($null -eq $this.Date) { return [string]::Empty } if ($($this.Date - [datetime]::Now) -ge [timespan]::new(0)) { return $this.Date.ToString(); } else { return 'Expired' } } } #endregion securecodes~Expiration #region Custom_EncClasses_&_Helpers #region BitwiseTools # .SYNOPSIS # BitwUtil class heps design encryption algorithms that use bitwise operations and non-linear transformations. # Its also used in the construction of the ChaCha20 cipher. Class BitwUtil { static [Byte[]] Prepend([Byte[]]$Bytes, [byte[]]$BytesToPrepend) { $tmp = New-Object byte[] $($bytes.Length + $bytesToPrepend.Length); #$tmp = [Byte[]] (, 0xFF * ($bytes.Length + $bytesToPrepend.Length)); $bytesToPrepend.CopyTo($tmp, 0); $bytes.CopyTo($tmp, $bytesToPrepend.Length); return $tmp; } static [byte[][]] Shift([byte[]]$Bytes, [int]$size) { $left = New-Object byte[] $size; $right = New-Object byte[] $($bytes.Length - $size); [Array]::Copy($bytes, 0, $left, 0, $left.Length); [Array]::Copy($bytes, $left.Length, $right, 0, $right.Length); return ($left, $right); } static [Int32] RotateLeft([Int32]$val, [Int32]$amount) { return (($val -shl $amount) -bor ($val -shr (32 - $amount))) } static [Int64] RotateLeft([Int64]$val, [Int64]$amount) { return (($val -shl $amount) -bor ($val -shr (32 - $amount))) } static [void] QuaterRound([ref]$a, [ref]$b, [ref]$c, [ref]$d) { $a.Value = $a.Value + $b.Value; $d.Value = [BitwUtil]::RotateLeft($d.Value -xor $a.Value, 16); $c.Value = $c.Value + $d.Value; $b.Value = [BitwUtil]::RotateLeft($b.Value -xor $c.Value, 12); $a.Value = $a.Value + $b.Value; $d.Value = [BitwUtil]::RotateLeft($d.Value -xor $a.Value, 8); $c.Value = $c.Value + $d.Value; $b.Value = [BitwUtil]::RotateLeft($b.Value -xor $c.Value, 7); } static [int32[]] QuaterRound([int32]$a, [int32]$b, [int32]$c, [int32]$d) { # /!\ WARNING /!\ Incomplete & Not Tested [int32]$dVal = [BitConverter]::ToInt32($d, 0) [int32]$bVal = [BitConverter]::ToInt32($b, 0) $a = $a + $b $dVal = $dVal -xor $a $dVal = $dVal -shl 16 $d = [BitConverter]::GetBytes($dVal) $c = $c + $d $bVal = $bVal -xor $c $bVal = $bVal -shl 12 $b = [BitConverter]::GetBytes($bVal) $a = $a + $b $dVal = $dVal -xor $a $dVal = $dVal -shl 8 $d = [BitConverter]::GetBytes($dVal) $c = $c + $d $bVal = $bVal -xor $c $bVal = $bVal -shl 7 $b = [BitConverter]::GetBytes($bVal) return [int32[]]@([int32][BitConverter]::ToInt32($a, 0), [int32][BitConverter]::ToInt32($b, 0), [int32][BitConverter]::ToInt32($c, 0), [int32][BitConverter]::ToInt32($d, 0)) } # MixColumns: performs operations on columns of an array static [byte[, ]] MixColumns([byte[, ]]$state) { [byte[]] $tmp = New-Object byte[] 4 for ($i = 0; $i -lt 4; $i++) { $tmp[0] = [Byte] ($state[0, $i] * 2 + $state[1, $i] * 3) $tmp[1] = [Byte] ($state[1, $i] * 2 + $state[2, $i] * 3) $tmp[2] = [Byte] ($state[2, $i] * 2 + $state[3, $i] * 3) $tmp[3] = [Byte] ($state[3, $i] * 2 + $state[0, $i] * 3) for ($j = 0; $j -lt 4; $j++) { $state[$j, $i] = $tmp[$j] } } return $state } # ShiftRows: Performs bitwise operations to shift elements in rows of the $state array static [int32[]] ShiftRows([int32[]]$state) { $temp = $state[1] $state[1] = $state[5] $state[5] = $state[9] $state[9] = $state[13] $state[13] = $temp $temp = $state[2] $state[2] = $state[10] $state[10] = $temp $temp = $state[6] $state[6] = $state[14] $state[14] = $temp $temp = $state[15] $state[15] = $state[11] $state[11] = $state[7] $state[7] = $state[3] $state[3] = $temp return $state } # KeyExpansion: generates an expanded key based on the original key static [int32[]] KeyExpansion([int32[]]$key, [int32]$rounds) { $expandedKey = New-Object int32[] $rounds*16 $temp = New-Object int32[] 4 $i = 0 while ($i -lt $rounds * 16) { $expandedKey[$i] = $key[$i % 4] if (($i % 4) -eq 3) { $temp = [BitwUtil]::KeyExpansionCore($temp, ($i / 4)) for ($j = 0; $j -lt 4; $j++) { $expandedKey[$i + $j] = $expandedKey[$i + $j - 4] -xor $temp[$j] } $i += 4 } $i++ } return $expandedKey } static hidden [int32[]] KeyExpansionCore([int32[]]$temp, [int32]$round) { $temp[0] = [int32]([BitConverter]::ToInt32($temp, 0) -shl 8 -xor $temp[0] -xor ($round -shl 24)) $temp[1] = [int32]([BitConverter]::ToInt32($temp, 4) -shl 8 -xor $temp[1]) $temp[2] = [int32]([BitConverter]::ToInt32($temp, 8) -shl 8 -xor $temp[2]) $temp[3] = [int32]([BitConverter]::ToInt32($temp, 12) -shl 8 -xor $temp[3] -xor $round) return $temp } # SubBytes: Performs a substitution operation on each byte of an array static [byte[]] SubBytes([byte[]]$state, [byte[]]$sBox) { # Note: $sBox is an array of 256 values representing the substitution box. # You'll need to initialize it with appropriate values for the specific encryption algorithm you're using. for ($i = 0; $i -lt $state.Length; $i++) { $state[$i] = $sBox[$state[$i]] } return $state } # AddRoundKey: performs bitwise operations to add elements from two arrays static [byte[]] AddRoundKey([byte[]]$state, [byte[]]$roundKey) { for ($i = 0; $i -lt $state.Length; $i++) { $state[$i] = $state[$i] -bxor $roundKey[$i] } return $state } static [Int64] Reduce([Double]$nput) { [Double]$max = 9223372036854775807 $result = $nput while ($result -ge $max) { $result = [double]($result / 2) } return $result } static [Int64[]] Reduce([Int64[]]$arr) { [Int64]$u = 0; # The overflow from each calculation of the reduction process [Int64[]]$h = $arr $u = $u + [BitwUtil]::Reduce($h[0]); $h[0] = $u -band 0xffffffc; $u = $u -shr 26; $u = $u + [BitwUtil]::Reduce($h[1]); $h[1] = $u -band 0xffffffc; $u = $u -shr 26; $u = $u + [BitwUtil]::Reduce($h[2]); $h[2] = $u -band 0xffffffc; $u = $u -shr 26; $u = $u + [BitwUtil]::Reduce($h[3]); $h[3] = $u -band 0xffffffc; $u = $u -shr 26; $u = $u + [BitwUtil]::Reduce($h[4]); $h[4] = $u -band 0xffffffc; $u = $u -shr 26; $h[0] = [BitwUtil]::Reduce($h[0]) + $u * 5; $u = $u -shr 2; # Write-Debug "ReduceOverflow: $u" -Debug return $h } [byte[]]ToLittleEndian([byte[]]$value) { if (![System.BitConverter]::IsLittleEndian) { [array]::Reverse($value) } return $value } # TODO: write InvMixColumns method: performs inverse operations on columns of an array # TODO: write InvShiftRows method: performs inverse bitwise operations to shift elements in rows of an array # TODO: write InvSubBytes method: performs an inverse substitution operation on each byte of an array # TODO: write InvAddRoundKey method: performs inverse bitwise operations to add elements from two arrays. } #endregion BitwiseTools #region Shuffl3r # .SYNOPSIS # Shuffles bytes and nonce into a jumbled byte[] mess that can be split using a password. # Can be used to Combine the encrypted data with the initialization vector (IV) and other data. # .DESCRIPTION # Everyone is appending the IV to encrypted bytes, such that when decrypting, $CryptoProvider.IV = $encyptedBytes[0..15]; # They say its safe since IV is basically random and changes every encryption. but this small loophole can allow an advanced attacker to use some tools to find that IV at the end. # This class aim to prevent that; or at least make it nearly impossible. # By using an int[] of indices as a lookup table to rearrange the $nonce and $bytes. # The int[] array is derrivated from the password that the user provides. # .EXAMPLE # $_bytes = [System.text.Encoding]::UTF8.GetBytes('** _H4ck_z3_W0rld_ **'); # $Nonce1 = [xcrypt]::GetRandomEntropy(); # $Nonce2 = [xcrypt]::GetRandomEntropy(); # $Passwd = 'OKay_&~rVJ+T?NpJ(8TqL' | xconvert ToSecurestring; # $shuffld = [Shuffl3r]::Combine([Shuffl3r]::Combine($_bytes, $Nonce2, $Passwd), $Nonce1, $Passwd); # ($b,$n1) = [Shuffl3r]::Split($shuffld, $Passwd, $Nonce1.Length); # ($b,$n2) = [Shuffl3r]::Split($b, $Passwd, $Nonce2.Length); # [System.text.Encoding]::UTF8.GetString($b) -eq '** _H4ck_z3_W0rld_ **' # should be $true class Shuffl3r { static [Byte[]] Combine([Byte[]]$Bytes, [Byte[]]$Nonce, [securestring]$Passwod) { return [Shuffl3r]::Combine($bytes, $Nonce, ($Passwod | xconvert Tostring)) } static [Byte[]] Combine([Byte[]]$Bytes, [Byte[]]$Nonce, [string]$Passw0d) { # if ($Bytes.Length -lt 16) { throw [InvalidArgumentException]::New('Bytes', 'Input bytes.length should be > 16. ie: $minLength = 17, since the common $nonce length is 16') } if ($bytes.Length -lt ($Nonce.Length + 1)) { Write-Debug "Bytes.Length = $($Bytes.Length) but Nonce.Length = $($Nonce.Length)" -Debug throw [System.ArgumentOutOfRangeException]::new("Nonce", 'Make sure $Bytes.length > $Nonce.Length') } if ([string]::IsNullOrWhiteSpace($Passw0d)) { throw [System.ArgumentNullException]::new('$Passw0d') } [int[]]$Indices = [int[]]::new($Nonce.Length); Set-Variable -Name Indices -Scope local -Visibility Public -Option ReadOnly -Value ([Shuffl3r]::GenerateIndices($Nonce.Length, $Passw0d, $bytes.Length)); [Byte[]]$combined = [Byte[]]::new($bytes.Length + $Nonce.Length); for ([int]$i = 0; $i -lt $Indices.Length; $i++) { $combined[$Indices[$i]] = $Nonce[$i] } $i = 0; $ir = (0..($combined.Length - 1)) | Where-Object { $_ -NotIn $Indices }; foreach ($j in $ir) { $combined[$j] = $bytes[$i]; $i++ } return $combined } static [array] Split([Byte[]]$ShuffledBytes, [securestring]$Passwod, [int]$NonceLength) { return [Shuffl3r]::Split($ShuffledBytes, ($Passwod | xconvert ToString), [int]$NonceLength); } static [array] Split([Byte[]]$ShuffledBytes, [string]$Passw0d, [int]$NonceLength) { if ($null -eq $ShuffledBytes) { throw [System.ArgumentNullException]::new('$ShuffledBytes') } if ([string]::IsNullOrWhiteSpace($Passw0d)) { throw [System.ArgumentNullException]::new('$Passw0d') } [int[]]$Indices = [int[]]::new([int]$NonceLength); Set-Variable -Name Indices -Scope local -Visibility Private -Option ReadOnly -Value ([Shuffl3r]::GenerateIndices($NonceLength, $Passw0d, ($ShuffledBytes.Length - $NonceLength))); $Nonce = [Byte[]]::new($NonceLength); $bytes = [Byte[]]$((0..($ShuffledBytes.Length - 1)) | Where-Object { $_ -NotIn $Indices } | Select-Object *, @{l = 'bytes'; e = { $ShuffledBytes[$_] } }).bytes for ($i = 0; $i -lt $NonceLength; $i++) { $Nonce[$i] = $ShuffledBytes[$Indices[$i]] }; return ($bytes, $Nonce) } static hidden [int[]] GenerateIndices([int]$Count, [string]$randomString, [int]$HighestIndex) { if ($HighestIndex -lt 3 -or $Count -ge $HighestIndex) { throw [System.ArgumentOutOfRangeException]::new('$HighestIndex >= 3 is required; and $Count should be less than $HighestIndex') } if ([string]::IsNullOrWhiteSpace($randomString)) { throw [System.ArgumentNullException]::new('$randomString') } [Byte[]]$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes([string]$randomString)) [int[]]$indices = [int[]]::new($Count) for ($i = 0; $i -lt $Count; $i++) { [int]$nextIndex = [Convert]::ToInt32($hash[$i] % $HighestIndex) while ($indices -contains $nextIndex) { $nextIndex = ($nextIndex + 1) % $HighestIndex } $indices[$i] = $nextIndex } return $indices } } #endregion Shuffl3r #region AesGCM # .SYNOPSIS # A custom AesCGM class, with nerdy Options like compression, iterrations, protection ... # .DESCRIPTION # Both AesCng and AesGcm are secure encryption algorithms, but AesGcm is generally considered to be more secure than AesCng in most scenarios. # AesGcm is an authenticated encryption mode that provides both confidentiality and integrity protection. It uses a Galois/Counter Mode (GCM) to encrypt the data, and includes an authentication tag that protects against tampering with or forging the ciphertext. # AesCng, on the other hand, only provides confidentiality protection and does not include an authentication tag. This means that an attacker who can modify the ciphertext may be able to undetectably alter the decrypted plaintext. # Therefore, it is recommended to use AesGcm whenever possible, as it provides stronger security guarantees compared to AesCng. # .EXAMPLE # $bytes = 'Text_Message1' | xconvert ToBytes; $Password = 'X-aP0jJ_:No=08TfdQ' | xconvert ToSecurestring; $salt = [xcrypt]::GetRandomEntropy(); # $enc = [AesGCM]::Encrypt($bytes, $Password, $salt) # $dec = [AesGCM]::Decrypt($enc, $Password, $salt) # echo ([System.Text.Encoding]::UTF8.GetString($dec).Trim()) # should be: Text_Message1 # .EXAMPLE # $bytes = [System.Text.Encoding]::UTF8.GetBytes("S3crEt message...") # $enc = [Aesgcm]::Encrypt($bytes, (Read-Host -AsSecureString -Prompt "Encryption Password"), 4) # encrypt 4 times! # $secmessage = [convert]::ToBase64String($enc) # # # On recieving PC: # $dec = [AesGcm]::Decrypt([convert]::FromBase64String($secmessage), (Read-Host -AsSecureString -Prompt "Decryption Password"), 4) # echo ([System.Text.Encoding]::UTF8.GetString($dec)) # should be: S3crEt message... # .NOTES # Todo: Find a working/cross-platform way to protect bytes (Like DPAPI for windows but better) then # add static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [bool]$Protect, [string]$Compression, [int]$iterations) class AesGCM : xcrypt { # static hidden [byte[]]$_salt = [convert]::FromBase64String("hsKgmva9wZoDxLeREB1udw=="); static hidden [EncryptionScope] $Scope = [EncryptionScope]::User static [byte[]] Encrypt([byte[]]$bytes) { return [AesGCM]::Encrypt($bytes, [AesGCM]::GetPassword()); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password) { [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password) return [AesGCM]::Encrypt($bytes, $Password, $_salt); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt) { return [AesGCM]::Encrypt($bytes, $Password, $Salt, $null, $null, 1); } static [string] Encrypt([string]$text, [SecureString]$Password, [int]$iterations) { return [convert]::ToBase64String([AesGCM]::Encrypt([System.Text.Encoding]::UTF8.GetBytes("$text"), $Password, $iterations)); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations) { [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password) return [AesGCM]::Encrypt($bytes, $Password, $_salt, $null, $null, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [int]$iterations) { return [AesGCM]::Encrypt($bytes, $Password, $Salt, $null, $null, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations, [string]$Compression) { [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password) return [AesGCM]::Encrypt($bytes, $Password, $_salt, $null, $Compression, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [int]$iterations) { return [AesGCM]::Encrypt($bytes, $Password, $Salt, $associatedData, $null, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData) { return [AesGCM]::Encrypt($bytes, $Password, $Salt, $associatedData, $null, 1); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [string]$Compression, [int]$iterations) { [int]$IV_SIZE = 0; Set-Variable -Name IV_SIZE -Scope Local -Visibility Private -Option Private -Value 12 [int]$TAG_SIZE = 0; Set-Variable -Name TAG_SIZE -Scope Local -Visibility Private -Option Private -Value 16 [string]$Key = $null; Set-Variable -Name Key -Scope Local -Visibility Private -Option Private -Value $([convert]::ToBase64String([System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Password | xconvert ToString), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(32))); [System.IntPtr]$th = [System.IntPtr]::new(0); if ([string]::IsNullOrWhiteSpace([AesGCM]::caller)) { [AesGCM]::caller = '[AesGCM]' } Set-Variable -Name th -Scope Local -Visibility Private -Option Private -Value $([System.Runtime.InteropServices.Marshal]::StringToHGlobalAnsi($TAG_SIZE)); try { $_bytes = $bytes; $aes = $null; Set-Variable -Name aes -Scope Local -Visibility Private -Option Private -Value $([ScriptBlock]::Create("[Security.Cryptography.AesGcm]::new([convert]::FromBase64String('$Key'))").Invoke()); for ($i = 1; $i -lt $iterations + 1; $i++) { # Write-Host "$([AesGCM]::caller) [+] Encryption [$i/$iterations] ... Done" -ForegroundColor Yellow # if ($Protect) { $_bytes = Protect-Data -bytes $_bytes -entropy $Salt -scope User } # Generate a random IV for each iteration: [byte[]]$IV = $null; Set-Variable -Name IV -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes($IV_SIZE)); $tag = [byte[]]::new($TAG_SIZE); $Encrypted = [byte[]]::new($_bytes.Length); [void]$aes.Encrypt($IV, $_bytes, $Encrypted, $tag, $associatedData); $_bytes = [Shuffl3r]::Combine([Shuffl3r]::Combine($Encrypted, $IV, $Password), $tag, $Password); } } catch { throw $_ } finally { [void][System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocAnsi($th); Remove-Variable IV_SIZE, TAG_SIZE, th -ErrorAction SilentlyContinue } if (![string]::IsNullOrWhiteSpace($Compression)) { $_bytes = Compress-Data -b $_bytes -c $Compression } return $_bytes } static [void] Encrypt([IO.FileInfo]$File) { [AesGCM]::Encrypt($File, [AesGCM]::GetPassword()); } static [void] Encrypt([IO.FileInfo]$File, [securestring]$Password) { [AesGCM]::Encrypt($File, $Password, $null); } static [void] Encrypt([IO.FileInfo]$File, [securestring]$Password, [string]$OutPath) { [AesGCM]::Encrypt($File, $password, $OutPath, 1, $null); } static [void] Encrypt([IO.FileInfo]$File, [securestring]$Password, [string]$OutPath, [int]$iterations) { [AesGCM]::Encrypt($File, $password, $OutPath, $iterations, $null); } static [void] Encrypt([IO.FileInfo]$File, [securestring]$Password, [string]$OutPath, [int]$iterations, [string]$Compression) { [ValidateNotNullOrEmpty()][IO.FileInfo]$File = [AesGCM]::GetResolvedPath($File.FullName); if ([string]::IsNullOrWhiteSpace($OutPath)) { $OutPath = $File.FullName } [ValidateNotNullOrEmpty()][string]$OutPath = [AesGCM]::GetUnResolvedPath($OutPath); if (![string]::IsNullOrWhiteSpace($Compression)) { [AesGCM]::ValidateCompression($Compression) } $streamReader = [System.IO.FileStream]::new($File.FullName, [System.IO.FileMode]::Open) $ba = [byte[]]::New($streamReader.Length); [void]$streamReader.Read($ba, 0, [int]$streamReader.Length); [void]$streamReader.Close(); Write-Verbose "$([AesGCM]::caller) Begin file encryption:" Write-Verbose "[-] File : $File" Write-Verbose "[-] OutFile : $OutPath" [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password); $encryptdbytes = [AesGCM]::Encrypt($ba, $Password, $_salt, $null, $Compression, $iterations) $streamWriter = [System.IO.FileStream]::new($OutPath, [System.IO.FileMode]::OpenOrCreate); [void]$streamWriter.Write($encryptdbytes, 0, $encryptdbytes.Length); [void]$streamWriter.Close() [void]$streamReader.Dispose() [void]$streamWriter.Dispose() } static [byte[]] Decrypt([byte[]]$bytes) { return [AesGCM]::Decrypt($bytes, [AesGCM]::GetPassword()); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password) { [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password) return [AesGCM]::Decrypt($bytes, $Password, $_salt); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt) { return [AesGCM]::Decrypt($bytes, $Password, $Salt, $null, $null, 1); } static [string] Decrypt([string]$text, [SecureString]$Password, [int]$iterations) { return [System.Text.Encoding]::UTF8.GetString([AesGCM]::Decrypt([convert]::FromBase64String($text), $Password, $iterations)); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations) { [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password) return [AesGCM]::Decrypt($bytes, $Password, $_salt, $null, $null, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [int]$iterations) { return [AesGCM]::Decrypt($bytes, $Password, $Salt, $null, $null, 1); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations, [string]$Compression) { [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password) return [AesGCM]::Decrypt($bytes, $Password, $_salt, $null, $Compression, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [int]$iterations) { return [AesGCM]::Decrypt($bytes, $Password, $Salt, $associatedData, $null, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData) { return [AesGCM]::Decrypt($bytes, $Password, $Salt, $associatedData, $null, 1); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [string]$Compression, [int]$iterations) { [int]$IV_SIZE = 0; Set-Variable -Name IV_SIZE -Scope Local -Visibility Private -Option Private -Value 12 [int]$TAG_SIZE = 0; Set-Variable -Name TAG_SIZE -Scope Local -Visibility Private -Option Private -Value 16 [string]$Key = $null; Set-Variable -Name Key -Scope Local -Visibility Private -Option Private -Value $([convert]::ToBase64String([System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Password | xconvert ToString), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(32))); [System.IntPtr]$th = [System.IntPtr]::new(0); if ([string]::IsNullOrWhiteSpace([AesGCM]::caller)) { [AesGCM]::caller = '[AesGCM]' } Set-Variable -Name th -Scope Local -Visibility Private -Option Private -Value $([System.Runtime.InteropServices.Marshal]::StringToHGlobalAnsi($TAG_SIZE)); try { $_bytes = if (![string]::IsNullOrWhiteSpace($Compression)) { Expand-Data -bytes $bytes -compression $Compression } else { $bytes } $aes = [ScriptBlock]::Create("[Security.Cryptography.AesGcm]::new([convert]::FromBase64String('$Key'))").Invoke() for ($i = 1; $i -lt $iterations + 1; $i++) { # Write-Host "$([AesGCM]::caller) [+] Decryption [$i/$iterations] ... Done" -ForegroundColor Yellow # if ($UnProtect) { $_bytes = UnProtect-Data -bytes $_bytes -entropy $Salt -scope User } # Split the real encrypted bytes from nonce & tags then decrypt them: ($b, $n1) = [Shuffl3r]::Split($_bytes, $Password, $TAG_SIZE); ($b, $n2) = [Shuffl3r]::Split($b, $Password, $IV_SIZE); $Decrypted = [byte[]]::new($b.Length); $aes.Decrypt($n2, $b, $n1, $Decrypted, $associatedData); $_bytes = $Decrypted; } } catch { if ($_.FullyQualifiedErrorId -eq "AuthenticationTagMismatchException") { Write-Host "$([AesGCM]::caller) Wrong password" -ForegroundColor Yellow } throw $_ } finally { [void][System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocAnsi($th); Remove-Variable IV_SIZE, TAG_SIZE, th -ErrorAction SilentlyContinue } return $_bytes } static [void] Decrypt([IO.FileInfo]$File) { [AesGCM]::Decrypt($File, [AesGCM]::GetPassword()); } static [void] Decrypt([IO.FileInfo]$File, [securestring]$password) { [AesGCM]::Decrypt($File, $password, $null); } static [void] Decrypt([IO.FileInfo]$File, [securestring]$Password, [string]$OutPath) { [AesGCM]::Decrypt($File, $password, $OutPath, 1, $null); } static [void] Decrypt([IO.FileInfo]$File, [securestring]$Password, [string]$OutPath, [int]$iterations) { [AesGCM]::Decrypt($File, $password, $OutPath, $iterations, $null); } static [void] Decrypt([IO.FileInfo]$File, [securestring]$Password, [string]$OutPath, [int]$iterations, [string]$Compression) { [ValidateNotNullOrEmpty()][IO.FileInfo]$File = [AesGCM]::GetResolvedPath($File.FullName); if ([string]::IsNullOrWhiteSpace($OutPath)) { $OutPath = $File.FullName } [ValidateNotNullOrEmpty()][string]$OutPath = [AesGCM]::GetUnResolvedPath($OutPath); if (![string]::IsNullOrWhiteSpace($Compression)) { [AesGCM]::ValidateCompression($Compression) } $streamReader = [System.IO.FileStream]::new($File.FullName, [System.IO.FileMode]::Open) $ba = [byte[]]::New($streamReader.Length); [void]$streamReader.Read($ba, 0, [int]$streamReader.Length); [void]$streamReader.Close(); Write-Verbose "$([AesGCM]::caller) Begin file decryption:" Write-Verbose "[-] File : $File" Write-Verbose "[-] OutFile : $OutPath" [byte[]]$_salt = [AesGCM]::GetDerivedBytes($Password); $decryptdbytes = [AesGCM]::Decrypt($ba, $Password, $_salt, $null, $Compression, $iterations) $streamWriter = [System.IO.FileStream]::new($OutPath, [System.IO.FileMode]::OpenOrCreate); [void]$streamWriter.Write($decryptdbytes, 0, $decryptdbytes.Length); [void]$streamWriter.Close() [void]$streamReader.Dispose() [void]$streamWriter.Dispose() } } #endregion AesGCM #region AesCng # .SYNOPSIS # A custom System.Security.Cryptography.AesCng class, for more control on hashing, compression & other stuff. # .DESCRIPTION # A symmetric-key encryption algorithm that is used to protect a variety of sensitive data, including financial transactions and government communications. # It is considered to be very secure, and has been adopted as a standard by many governments and organizations around the world. # # Just as [System.Security.Cryptography.AesCng], by default this class CBC ciphermode, PKCS7 padding, and 256b key & SHA1 to hash (since it has been proven to be more secure than MD5). # Plus there is the option to stack encryptions by iteration. (But beware when you iterate much it produces larger output) class AesCng : xcrypt { static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password) { return [AesCng]::Encrypt($Bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw==')); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt) { return [AesCng]::Encrypt($Bytes, $Password, $Salt, 'Gzip', $false); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [bool]$Protect) { return [AesCng]::Encrypt($Bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw=='), 'Gzip', $Protect); } static [byte[]] Encrypt([byte[]]$Bytes, [securestring]$Password, [int]$iterations) { return [AesCng]::Encrypt($Bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw=='), $iterations) } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [bool]$Protect) { return [AesCng]::Encrypt($Bytes, $Password, $Salt, 'Gzip', $Protect); } static [byte[]] Encrypt([byte[]]$Bytes, [securestring]$Password, [byte[]]$Salt, [int]$iterations) { if ($null -eq $Bytes) { throw [System.ArgumentNullException]::new('bytes', 'Bytes Value cannot be null.') } $_bytes = $Bytes; if ([string]::IsNullOrWhiteSpace([AesGCM]::caller)) { [AesCng]::caller = '[AesCng]' } for ($i = 1; $i -lt $iterations + 1; $i++) { Write-Host "$([AesCng]::caller) [+] Encryption [$i/$iterations] ...$( $_bytes = [AesCng]::Encrypt($_bytes, $Password, $Salt) ) Done." -ForegroundColor Yellow }; return $_bytes; } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [string]$Compression) { return [AesCng]::Encrypt($Bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw=='), $Compression, $false); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [string]$Compression) { return [AesCng]::Encrypt($Bytes, $Password, $Salt, $Compression, $false); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [string]$Compression, [bool]$Protect) { [int]$KeySize = 256; $CryptoProvider = $null; $EncrBytes = $null if ($Compression -notin ([Enum]::GetNames('Compression' -as 'Type'))) { Throw [System.InvalidCastException]::new("The name '$Compression' is not a valid [Compression]`$typeName.") } Set-Variable -Name CryptoProvider -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.AesCryptoServiceProvider]::new()); $CryptoProvider.KeySize = [int]$KeySize; $CryptoProvider.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7; $CryptoProvider.Mode = [System.Security.Cryptography.CipherMode]::CBC; $CryptoProvider.Key = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Password | xconvert ToString), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes($KeySize / 8); $CryptoProvider.IV = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(16); Set-Variable -Name EncrBytes -Scope Local -Visibility Private -Option Private -Value $([Shuffl3r]::Combine($CryptoProvider.CreateEncryptor().TransformFinalBlock($Bytes, 0, $Bytes.Length), $CryptoProvider.IV, $Password)); if ($Protect) { $EncrBytes = Protect-Data -Bytes $encrBytes -Entropy $Salt -Scope User } Set-Variable -Name EncrBytes -Scope Local -Visibility Private -Option Private -Value $(Expand-Data -bytes $EncrBytes -compression $Compression); $CryptoProvider.Clear(); $CryptoProvider.Dispose() return $EncrBytes } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password) { return [AesCng]::Decrypt($bytes, $Password, 'Gzip'); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt) { return [AesCng]::Decrypt($bytes, $Password, $Salt, 'GZip', $false); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [bool]$UnProtect) { return [AesCng]::Decrypt($bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw=='), 'GZip', $UnProtect); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations) { return [AesCng]::Decrypt($Bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw=='), $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [bool]$UnProtect) { return [AesCng]::Decrypt($bytes, $Password, $Salt, 'GZip', $UnProtect); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$salt, [int]$iterations) { if ($null -eq $bytes) { throw [System.ArgumentNullException]::new('bytes', 'Bytes Value cannot be null.') } $_bytes = $bytes; if ([string]::IsNullOrWhiteSpace([AesCng]::caller)) { [AesCng]::caller = '[AesCng]' } for ($i = 1; $i -lt $iterations + 1; $i++) { Write-Host "$([AesCng]::caller) [+] Decryption [$i/$iterations] ...$( $_bytes = [AesCng]::Decrypt($_bytes, $Password, $salt) ) Done" -ForegroundColor Yellow }; return $_bytes } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [string]$Compression) { return [AesCng]::Decrypt($bytes, $Password, [Convert]::FromBase64String('bz07LmY5XiNkXW1WQjxdXw=='), $Compression, $false); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [string]$Compression) { return [AesCng]::Decrypt($bytes, $Password, $Salt, $Compression, $false); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [string]$Compression, [bool]$UnProtect) { [int]$KeySize = 256; $CryptoProvider = $null; $DEcrBytes = $null; $_Bytes = $null $_Bytes = Expand-Data -b $bytes -c $Compression; if ($UnProtect) { $_Bytes = UnProtect-Data -Bytes $_Bytes -Entropy $Salt -Scope User } Set-Variable -Name CryptoProvider -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.AesCryptoServiceProvider]::new()); $CryptoProvider.KeySize = $KeySize; $CryptoProvider.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7; $CryptoProvider.Mode = [System.Security.Cryptography.CipherMode]::CBC; $CryptoProvider.Key = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Password | xconvert ToString), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes($KeySize / 8); ($_Bytes, $CryptoProvider.IV) = [Shuffl3r]::Split($_Bytes, $Password, 16); Set-Variable -Name DEcrBytes -Scope Local -Visibility Private -Option Private -Value $($CryptoProvider.CreateDecryptor().TransformFinalBlock($_Bytes, 0, $_Bytes.Length)) $CryptoProvider.Clear(); $CryptoProvider.Dispose(); return $DEcrBytes } } #endregion AesCng #region AesCtr # .SYNOPSIS # A custom implementation of AES-ctr. # .DESCRIPTION # [System.Security.Cryptography.CipherMode]::CTR is not available in PowerShell. # This class implements the CTR mode manually by XOR-ing with a sequence of counter blocks generated from the given nonce (IV) and block count. # .NOTES # I found out that, in practice it is recommended to use a more secure encryption mode, such as `[System.Security.Cryptography.CipherMode]::GCM` instead of a manual implementing the CTR mode. # So I gave it up and I dont use/recommend this class. what a waste! class AesCtr : xcrypt { static hidden [byte[]]$counter static [Byte[]] Encrypt([Byte[]]$Bytes, [byte[]]$Key, [byte[]]$IV) { $aes = [System.Security.Cryptography.AesCryptoServiceProvider]::new() $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC $aes.Key = $Key $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 [AesCtr]::counter = [Byte[]]::new($aes.BlockSize / 8) [Array]::Copy($IV, 0, [AesCtr]::counter, 0, $IV.Length) [BitConverter]::GetBytes([AesCtr]::counter).CopyTo([AesCtr]::counter, [AesCtr]::counter.Length - 8) [Byte[]]$CipherBytes = [Byte[]]::new($Bytes.Length) for ($i = 0; $i -lt $Bytes.Length; $i += $aes.BlockSize / 8) { [Byte[]]$counterBlock = $aes.CreateEncryptor().TransformFinalBlock([AesCtr]::counter, 0, [AesCtr]::counter.Length); [Array]::Copy($counterBlock, 0, $CipherBytes, $i, [Math]::Min($counterBlock.Length, $Bytes.Length - $i)); [AesCtr]::counter++ [BitConverter]::GetBytes([AesCtr]::counter).CopyTo([AesCtr]::counter, [AesCtr]::counter.Length - 8); } for ($i = 0; $i -lt $Bytes.Length; $i++) { $CipherBytes[$i] = $CipherBytes[$i] -bxor $Bytes[$i] } return $CipherBytes } static [Byte[]] Decrypt([Byte[]]$Bytes, [byte[]]$Key, [byte[]]$IV) { $aes = [System.Security.Cryptography.AesCryptoServiceProvider]::new() $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC $aes.Key = $Key $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 [AesCtr]::counter = [Byte[]]::new($aes.BlockSize / 8) [Array]::Copy($IV, 0, [AesCtr]::counter, 0, $IV.Length) [BitConverter]::GetBytes([AesCtr]::counter).CopyTo([AesCtr]::counter, [AesCtr]::counter.Length - 8) [Byte[]]$decrypted = [Byte[]]::new($Bytes.Length) for ($i = 0; $i -lt $Bytes.Length; $i += $aes.BlockSize / 8) { [Byte[]]$counterBlock = $aes.CreateEncryptor().TransformFinalBlock([AesCtr]::counter, 0, [AesCtr]::counter.Length) [Array]::Copy($counterBlock, 0, $decrypted, $i, [Math]::Min($counterBlock.Length, $Bytes.Length - $i)) [AesCtr]::counter++ [BitConverter]::GetBytes([AesCtr]::counter).CopyTo([AesCtr]::counter, [AesCtr]::counter.Length - 8) } for ($i = 0; $i -lt $decrypted.Length; $i++) { $decrypted[$i] = $Bytes[$i] -bxor $decrypted[$i] } return $decrypted } } #endregion AesCtr #region RSA # .SYNOPSIS # Powershell class implementation of RSA (Rivest-Shamir-Adleman) algorithm. # .DESCRIPTION # A public-key cryptosystem that is widely used for secure data transmission. It is based on the mathematical concept of factoring large composite numbers into their prime factors. The security of the RSA algorithm is based on the difficulty of factoring large composite numbers, which makes it computationally infeasible for an attacker to determine the private key from the public key. # .EXAMPLE # Test-MyTestFunction -Verbose # Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines class RSA : xcrypt { # Simply Encrypts the specified data using the public key. static [byte[]] Encrypt([byte[]]$data, [string]$publicKeyXml) { $rsa = [System.Security.Cryptography.RSACryptoServiceProvider]::new() $rsa.FromXmlString($publicKeyXml) return $rsa.Encrypt($data, $true) } # Decrypts the specified data using the private key. static [byte[]] Decrypt([byte[]]$data, [string]$privateKeyXml) { $rsa = [System.Security.Cryptography.RSACryptoServiceProvider]::new() $rsa.FromXmlString($privateKeyXml) return $rsa.Decrypt($data, $true) } # The data is encrypted using AES in combination with the password and salt. # The encrypted data is then encrypted using RSA. static [byte[]] Encrypt([byte[]]$data, [string]$PublicKeyXml, [securestring]$password, [byte[]]$salt) { # Generate the AES key and initialization vector from the password and salt $aesKey = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1000).GetBytes(32); $aesIV = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1000).GetBytes(16); # Encrypt the data using AES $aes = [System.Security.Cryptography.AesCryptoServiceProvider]::new(); ($aes.Key, $aes.IV) = ($aesKey, $aesIV); $encryptedData = $aes.CreateEncryptor().TransformFinalBlock($data, 0, $data.Length) # Encrypt the AES key and initialization vector using RSA $rsa = [System.Security.Cryptography.RSACryptoServiceProvider]::new() $rsa.FromXmlString($PublicKeyXml) $encryptedKey = $rsa.Encrypt($aesKey, $true) $encryptedIV = $rsa.Encrypt($aesIV, $true) # Concatenate the encrypted key, encrypted IV, and encrypted data # and return the result as a byte array return [byte[]]([System.Linq.Enumerable]::Concat($encryptedKey, $encryptedIV, $encryptedData)); } # Decrypts the specified data using the private key. # The data is first decrypted using RSA to obtain the AES key and initialization vector. # The data is then decrypted using AES. static [byte[]] Decrypt([byte[]]$data, [string]$privateKeyXml, [securestring]$password) { # Extract the encrypted key, encrypted IV, and encrypted data from the input data $encryptedKey = $data[0..255] $encryptedIV = $data[256..271] $encryptedData = $data[272..$data.Length] # Decrypt the AES key and initialization vector using RSA $rsa = [System.Security.Cryptography.RSACryptoServiceProvider]::new() # todo: Use the $PASSWORD to decrypt the private key so it can be used $rsa.FromXmlString($privateKeyXml) $aesKey = $rsa.Decrypt($encryptedKey, $true) $aesIV = $rsa.Decrypt($encryptedIV, $true) # Decrypt the data using AES $aes = [System.Security.Cryptography.AesCryptoServiceProvider]::new() $aes.Key = $aesKey $aes.IV = $aesIV return $aes.CreateDecryptor().TransformFinalBlock($encryptedData, 0, $encryptedData.Length) } # Exports the key pair to a file or string. # This can be useful if you want to save the key pair to a file or string for later use. # If a file path is specified, the key pair will be saved to the file. # If no file path is specified, the key pair will be returned as a string. static [void] ExportKeyPair([xml]$publicKeyXml, [string]$privateKeyXml, [string]$filePath = "") { $keyPair = @{ "PublicKey" = $publicKeyXml "PrivateKey" = $privateKeyXml } if ([string]::IsNullOrWhiteSpace($filePath)) { throw 'Invalid FilePath' } else { # Save the key pair to the specified file $keyPair | ConvertTo-Json | Out-File -FilePath $filePath } } static [psobject] LoadKeyPair([string]$filePath = "" ) { if ([string]::IsNullOrWhiteSpace($filePath)) { throw [System.ArgumentNullException]::new('filePath') } return [RSA]::LoadKeyPair((Get-Content $filePath | ConvertFrom-Json)) } static [psobject] LoadKeyPair([string]$filePath = "", [string]$keyPairString = "") { return $keyPairString | ConvertFrom-Json } # Generates a new RSA key pair and returns the public and private key XML strings. [string] GenerateKeyPair() { $rsa = [System.Security.Cryptography.RSACryptoServiceProvider]::new() ($publicKey, $privateKey) = ($rsa.ToXmlString($false), $rsa.ToXmlString($true)) return $publicKey, $privateKey } } #endregion RSA #region X509 class X509 : xcrypt { static [System.Security.Cryptography.X509Certificates.X509Certificate2]CreateCertificate([string]$Subject, [string]$KeyUsage) { $upn = if ([bool](Get-Command git -ErrorAction SilentlyContinue)) { git config user.email } else { 'work@contoso.com' } return [X509]::CreateCertificate($Subject, $KeyUsage, $upn) } static [System.Security.Cryptography.X509Certificates.X509Certificate2]CreateCertificate([string]$Subject, [string]$KeyUsage, [string]$upn) { $pin = [xcrypt]::GetRandomSTR('01233456789', 3, 4, 4) | xconvert ToSecurestring $Extentions = @("2.5.29.17={text}upn=$upn") return [X509]::CreateCertificate($Subject, 2048, 60, "Cert:\CurrentUser\My", $Pin, 'ExportableEncrypted', 'Protect', $KeyUsage, $Extentions, $true) } static [System.Security.Cryptography.X509Certificates.X509Certificate2]CreateCertificate([string]$Subject, [string]$KeyUsage, [string[]]$Extentions) { $pin = [xcrypt]::GetRandomSTR('01233456789', 3, 4, 4) | xconvert ToSecurestring return [X509]::CreateCertificate($Subject, 2048, 60, "Cert:\CurrentUser\My", $Pin, 'ExportableEncrypted', 'Protect', $KeyUsage, $Extentions, $true) } static [System.Security.Cryptography.X509Certificates.X509Certificate2]CreateCertificate([string]$Subject, [string]$upn, [securestring]$pin, [string]$KeyUsage) { $Extentions = @("2.5.29.17={text}upn=$upn") return [X509]::CreateCertificate($Subject, 2048, 60, "Cert:\CurrentUser\My", $Pin, 'ExportableEncrypted', 'Protect', $KeyUsage, $Extentions, $true) } static [System.Security.Cryptography.X509Certificates.X509Certificate2]CreateCertificate([string]$Subject, [int]$keySizeInBits = 2048, [int]$ValidForInDays = 365, [string]$StoreLocation, [securestring]$Pin, [string]$KeyExportPolicy, [string]$KeyProtection, [string]$KeyUsage, [string[]]$Extentions, [bool]$IsCritical) { if (!($KeyExportPolicy -as [KeyExportPolicy] -is 'KeyExportPolicy')) { throw [InvalidArgumentException]::New('[Microsoft.CertificateServices.Commands.KeyExportPolicy]$KeyExportPolicy') } if (!($KeyProtection -as [KeyProtection] -is 'KeyProtection')) { throw [InvalidArgumentException]::New('[Microsoft.CertificateServices.Commands.KeyProtection]$KeyProtection') } if (!($keyUsage -as [KeyUsage] -is 'KeyUsage')) { throw [InvalidArgumentException]::New('[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]$KeyUsage') } if (![bool]("Microsoft.CertificateServices.Commands.KeyExportPolicy" -as [Type])) { Write-Verbose "[+] Load all necessary assemblies." # By Creating a dumy cert then remove it. This loads all necessary assemblies to create certificates; It worked for me! $DummyName = 'dummy-' + [Guid]::NewGuid().Guid; $DummyCert = New-SelfSignedCertificate -Type Custom -Subject "CN=$DummyName" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2", "2.5.29.17={text}upn=dummy@contoso.com") -KeyExportPolicy NonExportable -KeyUsage None -KeyAlgorithm RSA -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My"; $DummyCert.Dispose(); Get-ChildItem "Cert:\CurrentUser\My" | Where-Object { $_.subject -eq "CN=$DummyName" } | Remove-Item } $key = [System.Security.Cryptography.RSA]::Create($keySizeInBits) # Create a regular expression to match the DN (distinguishedName) format. ie: CN=CommonName,OU=OrganizationalUnit,O=Organization,L=Locality,S=State,C=Country $dnFormat = "^CN=.*,OU=.*,O=.*,L=.*,S=.*,C=.*"; # Ex: $subjN = "CN=My Cert Subject,OU=IT,O=MyCompany,L=MyCity,S=MyState,C=MyCountry" if ($subject -notmatch $dnFormat) { $Ip_Info = $(& ([scriptblock]::Create($((Invoke-RestMethod -Verbose:$false -Method Get https://api.github.com/gists/d1985ebe22fe07cc191c9458b3a2bdbc).files.'IpInfo.ps1'.content) + ';[Ipinfo]::getInfo()'))) $subject = "CN=$subject,OU=,O=,L=,S=,C="; $subject = $subject -replace "O=,", "O=Contoso,"; $subject = $subject -replace "OU=,", "OU=$keyUsage,"; $subject = $subject -replace "C=", "C=$($Ip_Info.country_name)"; $subject = $subject -replace "L=,", "L=$($Ip_Info.location.geoname_id),"; $subject = $subject -replace "S=,", "S=$($Ip_Info.city)," } # Set the OID (Object Identifier) for the subjectName object $subjectName = [System.Security.Cryptography.X509Certificates.X500DistinguishedName]::new($Subject); $subjectName.Oid = [System.Security.Cryptography.Oid]::new("1.2.840.10045.3.1.7"); $certRequest = [system.security.cryptography.x509certificates.certificaterequest]::new($subjectName, $key, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1); # Create an X509KeyUsageFlags object $X509KeyUsageFlags = [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::None $X509KeyUsageFlags = $X509KeyUsageFlags -bor ([System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::$KeyUsage); $notBefore = [System.DateTimeOffset]::Now.AddDays(-1); $notAfter = [System.DateTimeOffset]::Now.AddDays($ValidForInDays) $certRequest.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509Extension][System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new($X509KeyUsageFlags, $IsCritical)) foreach ($ext in $Extentions) { if ([X509]::IsValidExtension($ext)) { $oid, $val = $ext.Split("=") $extensionOid = [System.Security.Cryptography.Oid]::new($oid) $extsnrawData = [byte[]][System.Text.Encoding]::ASCII.GetBytes($val) $certRequest.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509Extension]::new($extensionOid, $extsnrawData, $IsCritical)) } else { throw [InvalidArgumentException]::New("$ext") } } Write-Verbose "[+] Creating SelfSigned Certificate ..." $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]$certRequest.CreateSelfSigned($notBefore, $notAfter); # Create an X509KeyStorageFlags object and set the KeyProtection value $x509ContentType, $x509KeyStorageFlags = switch ([string]::Join(':', $KeyExportPolicy, $KeyUsage, $KeyProtection)) { 'NonExportable:None:None' { ('Cert', 'UserKeySet'); break } 'NonExportable:DataEncipherment:Protect' { ('SerializedCertPfx', 'UserProtected'); break } 'ExportableEncrypted:DataEncipherment:Protect' { ('Pkcs12', 'Exportable'); break } 'ExportableEncrypted:DataEncipherment:ProtectHigh' { ('Pkcs12', 'UserProtected'); break } 'ExportableEncrypted:KeyEncipherment:Protect' { ('Pkcs12', 'Exportable'); break } 'ExportableEncrypted:CertSign:Protect' { ('SerializedStore', 'EphemeralKeySet'); break } 'Exportable:DataEncipherment:None' { ('Pkcs12', 'Exportable'); break } 'Exportable:DataEncipherment:Protect' { ('Pkcs12', 'UserProtected'); break } 'Exportable:DataEncipherment:ProtectHigh' { ('Pkcs12', 'EphemeralKeySet'); break } 'Exportable:KeyEncipherment:Protect' { ('SerializedCertPfx', 'Exportable'); break } 'Exportable:KeyEncipherment:ProtectHigh' { ('SerializedCertPfx', 'UserProtected'); break } 'Exportable:KeyAgreement:ProtectFingerPrint' { ('Pkcs7', 'MachineKeySet'); break } 'ExportableEncrypted:DecipherOnly:ProtectFingerPrint' { ('Authenticode', 'PersistKeySet'); break } 'NonExportable:CRLSign:None' { ('SerializedStore', 'UserKeySet'); break } 'NonExportable:NonRepudiation:Protect' { ('Pkcs7', 'UserProtected'); break } 'ExportableEncrypted:DigitalSignature:ProtectHigh' { ('SerializedStore', 'EphemeralKeySet'); break } 'Exportable:EncipherOnly:ProtectFingerPrint' { ('Pkcs7', 'UserProtected'); break } 'Default' { ('Unknown', 'DefaultKeySet') } } $x509ContentType = [System.Security.Cryptography.X509Certificates.X509ContentType]::$x509ContentType $x509KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::$x509KeyStorageFlags # if ($null -eq $Pin) { [securestring]$Pin = Read-Host -Prompt "New Certificate PIN" -AsSecureString } [byte[]]$certData = $cert.Export($x509ContentType, $Pin); # Import the certificate from the byte array and return the imported certificate [void]$cert.Import($certData, $Pin, $x509KeyStorageFlags); # Add the certificate to the personal store $store = [System.Security.Cryptography.X509Certificates.X509Store]::new([System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) [void]$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) [void]$store.Add($cert) [void]$store.Close() Write-Verbose "[+] Created $StoreLocation\$($cert.Thumbprint)" return $cert } static [byte[]] Encrypt([byte[]]$PlainBytes, [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) { $encryptor = $Cert.GetRSAPublicKey().CreateEncryptor() $cipherBytes = $encryptor.TransformFinalBlock($PlainBytes, 0, $PlainBytes.Length) return $cipherBytes } static [byte[]] Encrypt([byte[]]$PlainBytes, [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert, [System.Security.Cryptography.RSAEncryptionPadding]$KeyPadding) { $encryptor = $Cert.GetRSAPublicKey().CreateEncryptor($KeyPadding) $cipherBytes = $encryptor.TransformFinalBlock($PlainBytes, 0, $PlainBytes.Length) return $cipherBytes } static [byte[]] Decrypt([byte[]]$CipherBytes, [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) { $decryptor = $Cert.GetRSAPrivateKey().CreateDecryptor() $plainBytes = $decryptor.TransformFinalBlock($CipherBytes, 0, $CipherBytes.Length) return $plainBytes } static [byte[]] Decrypt([byte[]]$CipherBytes, [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert, [System.Security.Cryptography.RSAEncryptionPadding]$KeyPadding) { $decryptor = $Cert.GetRSAPrivateKey().CreateDecryptor($KeyPadding) $plainBytes = $decryptor.TransformFinalBlock($CipherBytes, 0, $CipherBytes.Length) return $plainBytes } static [bool]IsValidExtension([string] $extension) { # Regular expression to match the format of an extension string: "oid={text}value" $extensionFormat = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}={text}.*" return $extension -match $extensionFormat } static [System.Security.Cryptography.RSAEncryptionPadding]GetRSAPadding () { return $(& ([ScriptBlock]::Create("[System.Security.Cryptography.RSAEncryptionPadding]::$([Enum]::GetNames([RSAPadding]) | Get-Random)"))) } static [System.Security.Cryptography.RSAEncryptionPadding]GetRSAPadding([string]$Padding) { if (!(($Padding -as 'RSAPadding') -is [RSAPadding])) { throw "Value Not in Validateset." } return $(& ([ScriptBlock]::Create("[System.Security.Cryptography.RSAEncryptionPadding]::$Padding"))) } static [System.Security.Cryptography.RSAEncryptionPadding]GetRSAPadding([System.Security.Cryptography.RSAEncryptionPadding]$Padding) { [System.Security.Cryptography.RSAEncryptionPadding[]]$validPaddings = [Enum]::GetNames([RSAPadding]) | ForEach-Object { & ([ScriptBlock]::Create("[System.Security.Cryptography.RSAEncryptionPadding]::$_")) } if ($Padding -notin $validPaddings) { throw "Value Not in Validateset." } return $Padding } static [System.Security.Cryptography.X509Certificates.X509Certificate2] Import([string]$FilePath, [securestring]$Password) { return [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($FilePath, $Password); } static [void] Export([System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert, [string]$FilePath, [string]$Passw0rd) { $Cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $Passw0rd) | Set-Content -Path $FilePath -Encoding byte } } # static [void]Import() {} # static [string]Export([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert, [X509ContentType]$contentType) { # # Ex: # if ($contentType -eq 'PEM') { # $InsertLineBreaks = 1 # $oMachineCert = Get-Item Cert:\LocalMachine\My\1C0381278083E3CB026E46A7FF09FC4B79543D # $oPem = New-Object System.Text.StringBuilder # $oPem.AppendLine("-----BEGIN CERTIFICATE-----") # $oPem.AppendLine([System.Convert]::ToBase64String($oMachineCert.RawData, $InsertLineBreaks)) # $oPem.AppendLine("-----END CERTIFICATE-----") # $oPem.ToString() | Out-File D:\Temp\my.pem # # Or load a certificate from a file and convert it to pem format # $InsertLineBreaks = 1 # $sMyCert = "D:\temp\myCert.der" # $oMyCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($sMyCert) # $oPem = New-Object System.Text.StringBuilder # $oPem.AppendLine("-----BEGIN CERTIFICATE-----") # $oPem.AppendLine([System.Convert]::ToBase64String($oMyCert.RawData, $InsertLineBreaks)) # $oPem.AppendLine("-----END CERTIFICATE-----") # $oPem.ToString() | Out-File D:\Temp\my.pem # } # # $cert.Issuer = Get-CimInstance -ClassName Win32_UserAccount -Verbose:$false | Where-Object { $_.Name -eq $(whoami) } | Select-Object -ExpandProperty FullName # # X509Certificate2 pfxGeneratedCert = new X509Certificate2(generatedCert.Export(X509ContentType.Pfx)); # # has to be turned into pfx or Windows at least throws a security credentials not found during sslStream.connectAsClient or HttpClient request... # # return pfxGeneratedCert; # return '' # } #endregion X509 #region ecc # .SYNOPSIS # Elliptic Curve Cryptography # .DESCRIPTION # Asymmetric-key encryption algorithms that are known for their strong security and efficient use of resources. They are widely used in a variety of applications, including secure communication, file encryption, and password storage. # .EXAMPLE # $ecc = new ECC($publicKeyXml, $privateKeyXml) # $encryptedData = $ecc.Encrypt($data, $password, $salt) # $decryptedData = $ecc.Decrypt($encryptedData, $password, $salt) class ECC : xcrypt { $publicKeyXml = [string]::Empty $privateKeyXml = [string]::Empty # Constructor ECC([string]$publicKeyXml, [string]$privateKeyXml) { $this.publicKeyXml = $publicKeyXml $this.privateKeyXml = $privateKeyXml } # Encrypts the specified data using the public key. # The data is encrypted using AES in combination with the password and salt. # Normally I could use System.Security.Cryptography.ECCryptoServiceProvider but for Compatibility reasons # I use ECDsaCng class, which provides similar functionality. # The encrypted data is then encrypted using ECC. # Encrypts the specified data using the public key. # The data is encrypted using AES in combination with the password and salt. # The encrypted data is then encrypted using ECC. [byte[]] Encrypt([byte[]]$data, [securestring]$password, [byte[]]$salt) { # Generate the AES key and initialization vector from the password and salt $aesKey = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1000).GetBytes(32); $aesIV = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1000).GetBytes(16); # Encrypt the data using AES $aes = New-Object System.Security.Cryptography.AesCryptoServiceProvider $aes.Key = $aesKey $aes.IV = $aesIV $encryptedData = $aes.CreateEncryptor().TransformFinalBlock($data, 0, $data.Length) # Encrypt the AES key and initialization vector using ECC $ecc = New-Object System.Security.Cryptography.ECDsaCng $ecc.FromXmlString($this.publicKeyXml) $encryptedKey = $ecc.Encrypt($aesKey, $true) $encryptedIV = $ecc.Encrypt($aesIV, $true) # Concatenate the encrypted key, encrypted IV, and encrypted data # and return the result as a byte array return [byte[]]([System.Linq.Enumerable]::Concat($encryptedKey, $encryptedIV, $encryptedData)) # or: # $bytes = New-Object System.Collections.Generic.List[Byte] # $bytes.AddRange($encryptedKey) # $bytes.AddRange($encryptedIV) # $bytes.AddRange($encryptedData) # return [byte[]]$Bytes } # Decrypts the specified data using the private key. # The data is first decrypted using ECC to obtain the AES key and initialization vector. # The data is then decrypted using AES. [byte[]] Decrypt([byte[]]$data, [securestring]$password) { # Extract the encrypted key, encrypted IV, and encrypted data from the input data $encryptedKey = $data[0..255] $encryptedIV = $data[256..271] $encryptedData = $data[272..$data.Length] # Decrypt the AES key and initialization vector using ECC $ecc = [System.Security.Cryptography.ECDsaCng]::new(); $ecc.FromXmlString($this.privateKeyXml) $aesKey = $ecc.Decrypt($encryptedKey, $true) $aesIV = $ecc.Decrypt($encryptedIV, $true) # Decrypt the data using AES $aes = [System.Security.Cryptography.AesCryptoServiceProvider]::new(); $aes.Key = $aesKey $aes.IV = $aesIV return $aes.CreateDecryptor().TransformFinalBlock($encryptedData, 0, $encryptedData.Length) } # Generates a new ECC key pair and returns the public and private keys as XML strings. [string] GenerateKeyPair() { $ecc = [System.Security.Cryptography.ECDsaCng]::new(256) ($publicKey, $privateKey) = ($ecc.ToXmlString($false), $ecc.ToXmlString($true)) return $publicKey, $privateKey } # Exports the ECC key pair to a file or string. # If a file path is specified, the keys are saved to the file. # If a string is specified, the keys are returned as a string. # Usage: # $ECC.ExportKeyPair("C:\keys.xml") [string] ExportKeyPair([string]$file = $null) { # Create the key pair XML string $keyPairXml = " <keyPair> <publicKey>$($this.publicKeyXml)</publicKey> <privateKey>$($this.privateKeyXml)</privateKey> </keyPair> " # Save the key pair XML to a file or return it as a string if ($null -ne $file) { $keyPairXml | Out-File -Encoding UTF8 $file return $null } else { return $keyPairXml } } # Imports the ECC key pair from a file or string. # If a file path is specified, the keys are loaded from the file. # If a string is specified, the keys are loaded from the string. [void] ImportKeyPair([string]$filePath = $null, [string]$keyPairXml = $null) { # Load the key pair XML from a file or string if (![string]::IsNullOrWhiteSpace($filePath)) { if ([IO.File]::Exists($filePath)) { $keyPairXml = Get-Content -Raw -Encoding UTF8 $filePath } else { throw [System.IO.FileNotFoundException]::new('Unable to find the specified file.', "$filePath") } } else { throw [System.ArgumentNullException]::new('filePath') } # Extract the public and private key XML strings from the key pair XML $publicKey = ([xml]$keyPairXml).keyPair.publicKey $privateKey = ([xml]$keyPairXml).keyPair.privateKey # Set the public and private key XML strings in the ECC object $this.publicKeyXml = $publicKey $this.privateKeyXml = $privateKey } } #endregion ecc #region MD5 class MD5 : xcrypt { MD5() {} static [byte[]] Encrypt([byte[]]$data, [string]$hash) { $md5 = [System.Security.Cryptography.MD5CryptoServiceProvider]::new() $encoderShouldEmitUTF8Identifier = $false $encoder = [System.Text.UTF8Encoding]::new($encoderShouldEmitUTF8Identifier) $keys = [byte[]]$md5.ComputeHash($encoder.GetBytes($hash)); return [TripleDES]::Encrypt($data, $keys, $hash.Length); } static [byte[]] Decrypt([byte[]]$data, [string]$hash) { $md5 = [System.Security.Cryptography.MD5CryptoServiceProvider]::new() $encoderShouldEmitUTF8Identifier = $false $encoder = [System.Text.UTF8Encoding]::new($encoderShouldEmitUTF8Identifier) $keys = [byte[]]$md5.ComputeHash($encoder.GetBytes($hash)); return [TripleDES]::Decrypt($data, $keys, $hash.Length); } } #endregion MD5 #region TripleDES # .SYNOPSIS # Triple Des implementation in Powershell # .EXAMPLE # $t = [TripleDES]::new(3004) # $e = $t.Encrypt(30) # i.e: 30 times # [convert]::ToBase64String($e) > enc.txt # # # On the same PC # $n = [TripleDES]::new([convert]::FromBase64String($(Get-Content ./enc.txt))) # $d = $n.Decrypt(30) # echo (Bytes_To_Object($d)) # should be 3004 # .EXAMPLE # # Use static methods class TripleDES : xcrypt { [ValidateNotNullOrEmpty()][cPsObject]$Object; [ValidateNotNullOrEmpty()][SecureString]$Password; static hidden [byte[]] $Salt = [System.Text.Encoding]::UTF7.GetBytes('@Q:j9=`M?EV/h>9_M/esau>A)Y6h>/v^q\ZVMPH\Vu5/E"P_GN`#t6Wnf;ah~[dik.fkj7vpoSqqN]-u`tSS5o26?\u).6YF-9e_5-KQ%kf)A{P4a9/67J8v]:[%i8PW'); TripleDES([Object]$object) { $this.Object = [cPsObject]::new($object) $this.Password = [System.Text.Encoding]::UTF7.GetString([System.Security.Cryptography.Rfc2898DeriveBytes]::new([xcrypt]::GetUniqueMachineId(), [TripleDES]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(24)) | xconvert ToSecurestring } [byte[]]Encrypt() { return $this.Encrypt(1); } [byte[]]Encrypt([int]$iterations) { if ($null -eq $this.Object.Bytes) { throw ([System.ArgumentNullException]::new('Object.Bytes')) } if ($null -eq $this.Password) { throw ([System.ArgumentNullException]::new('Password')) } $this.Object.Psobject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create("[Convert]::FromBase64String('$([convert]::ToBase64String([TripleDES]::Encrypt($this.Object.Bytes, [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($this.Password | xconvert ToString), [TripleDES]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(24), $null, $iterations)))')"))); return $this.Object.Bytes } [byte[]]Decrypt() { return $this.Decrypt(1); } [byte[]]Decrypt([int]$iterations) { if ($null -eq $this.Object.Bytes) { throw ([System.ArgumentNullException]::new('Object.Bytes')) } if ($null -eq $this.Password) { throw ([System.ArgumentNullException]::new('Password')) } $this.Object.Psobject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create("[Convert]::FromBase64String('$([convert]::ToBase64String([TripleDES]::Decrypt($this.Object.Bytes, [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($this.Password | xconvert ToString), [TripleDES]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(24), $null, $iterations)))')"))); return $this.Object.Bytes } static [byte[]] Encrypt([Byte[]]$data, [Byte[]]$Key) { return [TripleDES]::Encrypt($data, $Key, $null, 1) } static [byte[]] Encrypt([Byte[]]$data, [Byte[]]$Key, [Byte[]]$IV) { return [TripleDES]::Encrypt($data, $Key, $IV, 1) } static [byte[]] Encrypt([Byte[]]$data, [Byte[]]$Key, [Byte[]]$IV, [int]$iterations) { for ($i = 1; $i -le $iterations; $i++) { $data = [TripleDES]::Get_ED($data, $Key, $IV, $true) } return $data } static [byte[]] Encrypt ([byte]$data, [securestring]$Password) { return [TripleDES]::Encrypt($data, $Password, 1); } static [byte[]] Encrypt ([byte]$data, [string]$Passw0rd, [int]$iterations) { return [TripleDES]::Encrypt($data, [System.Security.Cryptography.Rfc2898DeriveBytes]::new($Passw0rd, [TripleDES]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(24), $null, $iterations); } static [byte[]] Encrypt ([byte]$data, [securestring]$Password, [int]$iterations) { return [TripleDES]::Encrypt($data, ($Password | xconvert ToString), $iterations) } static [byte[]] Decrypt([Byte[]]$data, [Byte[]]$Key) { return [TripleDES]::Decrypt($data, $Key, $null, 1); } static [byte[]] Decrypt([Byte[]]$data, [Byte[]]$Key, [Byte[]]$IV) { return [TripleDES]::Decrypt($data, $Key, $IV, 1); } static [byte[]] Decrypt([Byte[]]$data, [Byte[]]$Key, [Byte[]]$IV, [int]$iterations) { for ($i = 1; $i -le $iterations; $i++) { $data = [TripleDES]::Get_ED($data, $Key, $IV, $false) } return $data } static [byte[]] Decrypt ([byte]$data, [securestring]$Password) { return [TripleDES]::Decrypt($data, $Password, 1) } static [byte[]] Decrypt ([byte]$data, [string]$Passw0rd, [int]$iterations) { return [TripleDES]::Decrypt($data, [System.Security.Cryptography.Rfc2898DeriveBytes]::new($Passw0rd, [TripleDES]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(24), $null, $iterations); } static [byte[]] Decrypt ([byte]$data, [securestring]$Password, [int]$iterations) { return [TripleDES]::Decrypt($data, ($Password | xconvert ToString), $iterations) } static hidden [byte[]] Get_ED([Byte[]]$data, [Byte[]]$Key, [Byte[]]$IV, [bool]$Encrypt) { $result = [byte[]]::new(0); $ms = [System.IO.MemoryStream]::new(); $cs = $null try { $tdes = [System.Security.Cryptography.TripleDESCryptoServiceProvider]::new() if ($null -eq $Key) { throw ([System.ArgumentNullException]::new('Key')) }else { $tdes.Key = $Key } if ($null -eq $IV) { $p4 = [xcrypt]::GetUniqueMachineId(); $tdes.IV = [System.Security.Cryptography.Rfc2898DeriveBytes]::new($p4, [TripleDES]::Salt, [int]([int[]][char[]]$p4 | Measure-Object -Sum).Sum, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(8) }else { $tdes.IV = $IV } $CryptoTransform = [System.Security.Cryptography.ICryptoTransform]$(if ($Encrypt) { $tdes.CreateEncryptor() }else { $tdes.CreateDecryptor() }) $cs = [System.Security.Cryptography.CryptoStream]::new($ms, $CryptoTransform, [System.Security.Cryptography.CryptoStreamMode]::Write) [void]$cs.Write($data, 0, $data.Length) [void]$cs.FlushFinalBlock() $ms.Position = 0 $result = [Byte[]]::new($ms.Length) [void]$ms.Read($result, 0, $ms.Length) } catch [System.Security.Cryptography.CryptographicException] { if ($_.exception.message -notlike "*data is not a complete block*") { throw $_.exception } } finally { Invoke-Command -ScriptBlock { $tdes.Clear(); $cs.Close(); $ms.Dispose() } -ErrorAction SilentlyContinue } return $result } } #endregion TripleDES #region XOR # .SYNOPSIS # Custom Xor implementation in Powershell # .EXAMPLE # $x = [XOR]::new("hello world!") # $e = $x.Encrypt(5) # i.e: 5 times # [convert]::ToBase64String($e) > xenc.txt # # # On the same PC # $n = [XoR]::new([convert]::FromBase64String($(Get-Content ./xenc.txt))) # $d = $n.Decrypt(5) # echo (Bytes_To_Object($d)) # should be hello world! # .EXAMPLE # # Use static methods class XOR : xcrypt { [ValidateNotNullOrEmpty()][cPsObject]$Object; [ValidateNotNullOrEmpty()][SecureString]$Password; static hidden [byte[]] $Salt = [System.Text.Encoding]::UTF7.GetBytes('\SBOv!^L?XuCFlJ%*[6(pUVp5GeR^|U=NH3FaK#XECOaM}ExV)3_bkd:eG;Z,tWZRMg;.A!,:-k6D!CP>74G+TW7?(\6;Li]lA**2P(a2XxL}<.*oJY7bOx+lD>%DVVa'); XOR([Object]$object) { $this.Object = [cPsObject]::new($object) $this.Password = [System.Text.Encoding]::UTF7.GetString([System.Security.Cryptography.Rfc2898DeriveBytes]::new([xcrypt]::GetUniqueMachineId(), [XOR]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(256 / 8)) | xconvert ToSecurestring } [byte[]] Encrypt() { return $this.Encrypt(1) } [byte[]] Encrypt([int]$iterations) { if ($null -eq $this.Object.Bytes) { throw ([System.ArgumentNullException]::new('Object.Bytes')) } if ($null -eq $this.Password) { throw ([System.ArgumentNullException]::new('key')) } $this.Object.Psobject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create("[Convert]::FromBase64String('$([convert]::ToBase64String([byte[]][XOR]::Encrypt($this.Object.Bytes, $this.Password, $iterations)))')"))) return $this.Object.Bytes } static [byte[]] Encrypt([byte[]]$Bytes, [String]$Passw0rd) { return [XOR]::Encrypt($bytes, ([System.Text.Encoding]::UTF7.GetString([System.Security.Cryptography.Rfc2898DeriveBytes]::new($Passw0rd, [XOR]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(256 / 8)) | xconvert ToSecurestring), 1) } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$password) { return [XOR]::Encrypt($bytes, $password, 1) } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$password, [int]$iterations) { $xorkey = $password | xconvert ToString, ToBytes; return [XOR]::Encrypt($bytes, $xorkey, $iterations) } static [byte[]] Encrypt([byte[]]$Bytes, [byte[]]$xorkey, [int]$iterations) { if ($null -eq $xorkey) { throw ([System.ArgumentNullException]::new('xorkey')) } $_bytes = $bytes; for ($i = 1; $i -lt $iterations + 1; $i++) { $_bytes = [XOR]::Get_ED($_bytes, $xorkey); }; if ($_bytes.Equals($bytes)) { $_bytes = $null } return $_bytes } [byte[]]Decrypt() { return $this.Decrypt(1) } [byte[]]Decrypt([int]$iterations) { if ($null -eq $this.Object.Bytes) { throw ([System.ArgumentNullException]::new('Object.Bytes')) } if ($null -eq $this.Password) { throw ([System.ArgumentNullException]::new('Password')) } $this.Object.Psobject.properties.add([psscriptproperty]::new('Bytes', [scriptblock]::Create("[Convert]::FromBase64String('$([convert]::ToBase64String([byte[]][XOR]::Decrypt($this.Object.Bytes, $this.Password, $iterations)))')"))); return $this.Object.Bytes } #!Not Recommended! static [byte[]] Decrypt([byte[]]$Bytes, [String]$Passw0rd) { return [XOR]::Decrypt($bytes, ([System.Text.Encoding]::UTF7.GetString([System.Security.Cryptography.Rfc2898DeriveBytes]::new($Passw0rd, [XOR]::Salt, 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(256 / 8)) | xconvert ToSecurestring), 1); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$password) { return [XOR]::Decrypt($bytes, $password, 1); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$password, [int]$iterations) { $xorkey = $password | xconvert ToString, ToBytes return [XOR]::Decrypt($bytes, $xorkey, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [byte[]]$xorkey, [int]$iterations) { if ($null -eq $xorkey) { throw ([System.ArgumentNullException]::new('xorkey')) } $_bytes = $bytes; for ($i = 1; $i -lt $iterations + 1; $i++) { $_bytes = [XOR]::Get_ED($_bytes, $xorkey) }; return $_bytes; } static hidden [byte[]] Get_ED([byte[]]$Bytes, [byte[]]$key) { return $(for ($i = 0; $i -lt $bytes.length) { for ($j = 0; $j -lt $key.length; $j++) { $bytes[$i] -bxor $key[$j] $i++ if ($i -ge $bytes.Length) { $j = $key.length } } } ) } } #endregion XOR #region RC4 # .SYNOPSIS # PowerShell class implementation of the RC4 algorithm # .DESCRIPTION # "Ron's Code 4" or "Rivest Cipher 4," depending on the source. # A symmetric key stream cipher that was developed by Ron Rivest of RSA Security in 1987. # It was widely used in the 1990s and early 2000s, but has since been replaced by more secure algorithms in many applications due to vulnerabilities. # .NOTES # RC4 is an old and insecure encryption algorithm. # It is recommended to use a more modern and secure algorithm, such as AES or ChaCha20. # But if you insist on using this, Just Use really strong passwords. # I mean shit like: Wwi@4c5w&@hOtf}Mm_t%&[BXq>5*0:Fm}6L'poyi!8LoZD\!HXPPPvMRas<CWl$yk${vlW9(f:S@w/E # .EXAMPLE # $dat = Bytes_From_Object("Hello World") # $enc = [rc4]::Encrypt($dat, (Read-Host -AsSecureString -Prompt 'Password')) # $dec = [rc4]::Decrypt($enc, (Read-Host -AsSecureString -Prompt 'Password')) # Bytes_To_Object($dec) class RC4 : xcrypt { static [Byte[]] Encrypt([Byte[]]$data, [Byte[]]$passwd) { $a = $i = $j = $k = $tmp = [Int]0 $key = [Int[]]::new(256) $box = [Int[]]::new(256) $cipher = [Byte[]]::new($data.Length) for ($i = 0; $i -lt 256; $i++) { $key[$i] = $passwd[$i % $passwd.Length]; $box[$i] = $i; } for ($j = $i = 0; $i -lt 256; $i++) { $j = ($j + $box[$i] + $key[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0; $i -lt $data.Length; $i++) { $a++; $a %= 256; $j += $box[$a]; $j %= 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $k = $box[(($box[$a] + $box[$j]) % 256)]; $cipher[$i] = [Byte]($data[$i] -bxor $k); } return $cipher; } static [Byte[]] Decrypt([Byte[]]$data, [Byte[]]$passwd) { return [RC4]::Encrypt($data, $passwd); # The Decrypt method simply calls the Encrypt method with the same arguments. # This is because the RC4 algorithm is symmetric, meaning that the same key is used for both encryption and decryption. # Therefore, the encryption and decryption processes are identical. } } #endregion RC4 #region CHACHA20 # .SYNOPSIS # An implementation of the ChaCha20 encryption algorithm in PowerShell. # .DESCRIPTION # Its derived from the Salsa20 stream cipher algorithm, with several changes like integrity checking to improve its security and performance. # ChaCha20 operates on a 256-bit key, a 64-bit nonce, and a 20-round pseudo-random number generator to generate keystream data. # The algorithm is highly efficient, making it well-suited for use in a wide range of applications, including encryption, digital signatures, and key derivation. # .NOTES # Encryption is a subtle and complex area and it's easy to make mistakes that can leave you vulnerable to attack. # While this implementation is functional, its not 100% tested. In a enterprise env practice, you should use a well-vetted implementation, like one from a reputable library. # You should also consider additional measures like key management, which are crucial for the security of the encryption and use a secure method for generating the key and nonce, such as using the System.Security.Cryptography.RNGCryptoServiceProvider class. # .LINK # https://github.com/alainQtec/cliHelper.Core/blob/main/Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1 # .EXAMPLE # $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider; $key = New-Object Byte[] 32; $rng.GetBytes($key); $IV = New-Object Byte[] 16; $rng.GetBytes($IV) # $bytes = [Text.Encoding]::UTF8.GetBytes("my secret message") # $enc = [chacha20]::Encrypt($bytes, $key, $IV) # $dec = [chacha20]::Decrypt($enc, $key, $IV) # echo ([Text.Encoding]::UTF8.GetString($dec)) # should be: my secret message # .EXAMPLE # $enc = [Chacha20]::Encrypt($bytes, (Read-Host -AsSecureString -Prompt "Passwd"), 5) # $dec = [chacha20]::Decrypt($enc, (Read-Host -AsSecureString -Prompt "Passwd"), 5) # echo ([Text.Encoding]::UTF8.GetString($dec)) Class ChaCha20 : xcrypt { static hidden [Int32]$blockSize = 64 static hidden [Byte[]]$SIGMA = [Byte[]]@(83, 72, 65, 67, 72, 65, 50, 48, 87, 65, 86, 69) static hidden [byte[]]$Salt = [convert]::FromBase64String('plqnkknbuujsklslyscgkycobvflyqwrttalqqjidosyjrodkiuxcokwjrftfyyttipfvtwodwrnvsre') static [byte[]] Encrypt([byte[]]$Bytes, [securestring]$Password) { return [Chacha20]::Encrypt($Bytes, $Password, [ChaCha20]::Salt) } static [Byte[]] Encrypt([Byte[]]$bytes, [Byte[]]$key, [Byte[]]$nonce) { # Write-Debug "Nptb64: $([convert]::ToBase64String($bytes))" -Debug [byte[]]$hash = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new().ComputeHash($bytes) [byte[]]$bytes = $bytes + $hash # Used for integrity check ie: We append a cryptographic hash (e.g., SHA-256) before encryption, and then check the hash of the decrypted plainBytes against the original hash after decryption. [Byte[]]$EncrBytes = [Byte[]]::new($bytes.Length) [Byte[]]$block = [Byte[]]::new([ChaCha20]::blockSize) [Int32]$bytesIndex = 0 [Int32]$EncrnIndex = 0 [Int32]$blockCounter = 0 while ($bytesIndex -lt $bytes.Length) { $block = [ChaCha20]::GenerateBlock($blockCounter, $key, $nonce) [Int32]$bytesToCopy = [Math]::Min($bytes.Length - $bytesIndex, [ChaCha20]::blockSize) for ([Int32]$i = 0; $i -lt $bytesToCopy; $i++) { $EncrBytes[$EncrnIndex + $i] = $bytes[$bytesIndex + $i] -bxor $block[$i] } $bytesIndex += $bytesToCopy $EncrnIndex += $bytesToCopy $blockCounter++ } # Write-Debug "Encb64: $([convert]::ToBase64String($EncrBytes))" -Debug return $EncrBytes } static [byte[]] Encrypt([byte[]]$Bytes, [securestring]$Password, [byte[]]$Salt) { return [Chacha20]::Encrypt($Bytes, $Password, $Salt, 1) } static [byte[]] Encrypt([byte[]]$Bytes, [securestring]$Password, [int]$iterations) { return [Chacha20]::Encrypt($Bytes, $Password, [ChaCha20]::Salt, $iterations) } static [byte[]] Encrypt([byte[]]$Bytes, [securestring]$Password, [byte[]]$Salt, [int]$iterations) { [byte[]]$Key = $null; Set-Variable -Name Key -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Password | xconvert ToString), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(32)); $_bytes = $bytes; if ([string]::IsNullOrWhiteSpace([ChaCha20]::caller)) { [ChaCha20]::caller = '[ChaCha20]' } for ($i = 1; $i -lt $iterations + 1; $i++) { Write-Host "$([ChaCha20]::caller) [+] Encryption [$i/$iterations] ...$( # Generate a random IV for each iteration: [byte[]]$IV = $null; Set-Variable -Name IV -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.Rfc2898DeriveBytes]::new(($password | xconvert ToString), $salt, 1, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(16)); $_bytes = [Shuffl3r]::Combine([Chacha20]::Encrypt($_bytes, $Key, $IV), $IV, $Password) ) Done" -ForegroundColor Yellow } return $_bytes } static [byte[]] Decrypt([byte[]]$Bytes, [securestring]$Password) { return [ChaCha20]::Decrypt($Bytes, $Password, [ChaCha20]::Salt) } static [byte[]] Decrypt([byte[]]$bytes, [byte[]]$key, [byte[]]$nonce) { [byte[]]$decrBytes = [byte[]]::new($bytes.Length); [Int32]$bytesIndex = 0 [Int32]$decrnIndex = 0 while ($bytesIndex -lt $bytes.Length) { [Int32]$blockCounter = $bytesIndex / [ChaCha20]::blockSize [Int32]$bytesToCopy = [Math]::Min($bytes.Length - $bytesIndex, [ChaCha20]::blockSize) [Byte[]]$block = [ChaCha20]::GenerateBlock($blockCounter, $key, $nonce) for ($i = 0; $i -lt $bytesToCopy; $i++) { $decrBytes[$decrnIndex + $i] = $bytes[$bytesIndex + $i] -bxor $block[$i] } $bytesIndex += [ChaCha20]::blockSize $decrnIndex += $bytesToCopy } $hash = $decrBytes | Select-Object -Last 32 $decr = $decrBytes | Select-Object -First ($decrBytes.Length - 32) if ([convert]::ToBase64String([System.Security.Cryptography.SHA256CryptoServiceProvider]::new().ComputeHash($decr)) -ne [convert]::ToBase64String($hash)) { throw [IntegrityCheckFailedException]"Integrity check failed" } # Write-Debug "Decb64: $([convert]::ToBase64String($decr))" -Debug return $decr } static [byte[]] Decrypt([byte[]]$Bytes, [securestring]$Password, [byte[]]$Salt) { return [ChaCha20]::Decrypt($Bytes, $Password, $Salt, 1) } static [byte[]] Decrypt([byte[]]$Bytes, [securestring]$Password, [int]$iterations) { return [ChaCha20]::Decrypt($Bytes, $Password, [ChaCha20]::Salt, $iterations) } static [byte[]] Decrypt([byte[]]$Bytes, [securestring]$Password, [byte[]]$Salt, [int]$iterations) { [byte[]]$Key = $null; Set-Variable -Name Key -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Password | xconvert ToString), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(32)); $_bytes = $bytes; if ([string]::IsNullOrWhiteSpace([ChaCha20]::caller)) { [ChaCha20]::caller = '[ChaCha20]' } for ($i = 1; $i -lt $iterations + 1; $i++) { Write-Host "$([ChaCha20]::caller) [+] Decryption [$i/$iterations] ...$( $bytes = $null; $IV = $null ($bytes, $IV) = [Shuffl3r]::Split($_bytes, $Password, 16); $_bytes = [Chacha20]::Decrypt($bytes, $Key, $IV) ) Done" -ForegroundColor Yellow } return $_bytes } static hidden [Byte[]] GenerateBlock([Int32]$blockCounter, [Byte[]]$key, [Byte[]]$nonce) { [Int32[]]$state = [Int32[]]@( 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574, # constant 0, 0, 0, 0, # block counter [BitConverter]::ToInt32($key[0..3], 0), [BitConverter]::ToInt32($key[4..7], 0), [BitConverter]::ToInt32($key[8..11], 0), [BitConverter]::ToInt32($key[12..15], 0), # key [BitConverter]::ToInt32($key[16..19], 0), [BitConverter]::ToInt32($key[20..23], 0), [BitConverter]::ToInt32($key[24..27], 0), [BitConverter]::ToInt32($key[28..31], 0), 0, 0, # nonce 0, 0 ) $state[12] = $blockCounter $state[14] = [BitConverter]::ToInt32($nonce[0..3], 0) $state[15] = [BitConverter]::ToInt32($nonce[4..7], 0) for ([Int32]$i = 0; $i -lt 10; $i++) { [BitwUtil]::QuaterRound([ref]$state[0], [ref]$state[4], [ref]$state[8], [ref]$state[12]) [BitwUtil]::QuaterRound([ref]$state[1], [ref]$state[5], [ref]$state[9], [ref]$state[13]) [BitwUtil]::QuaterRound([ref]$state[2], [ref]$state[6], [ref]$state[10], [ref]$state[14]) [BitwUtil]::QuaterRound([ref]$state[3], [ref]$state[7], [ref]$state[11], [ref]$state[15]) [BitwUtil]::QuaterRound([ref]$state[0], [ref]$state[5], [ref]$state[10], [ref]$state[15]) [BitwUtil]::QuaterRound([ref]$state[1], [ref]$state[6], [ref]$state[11], [ref]$state[12]) [BitwUtil]::QuaterRound([ref]$state[2], [ref]$state[7], [ref]$state[8], [ref]$state[13]) [BitwUtil]::QuaterRound([ref]$state[3], [ref]$state[4], [ref]$state[9], [ref]$state[14]) } [Byte[]]$result = [Byte[]]::new($state.Length * 4) for ([Int32]$i = 0; $i -lt $state.Length; $i++) { # Copy the bytes from the $i-th int in $state to the correct position in $result [Array]::Copy([BitConverter]::GetBytes($state[$i]), 0, $result, $i * 4, 4) } return $result } } #endregion CHACHA20 #region Poly1305 # .DESCRIPTION # Using Poly1305 for integrity checking is a better option than using SHA-256 in terms of security. # Poly1305 is a faster and more secure message authentication code (MAC) algorithm compared to SHA-256. # .NOTES # This Class would be amazing but it not functional; ...yet. Yeal its total bs :) class Poly1305 : xcrypt { [Byte[]]$Key Poly1305([Byte[]]$key) { if ($key.Length -ne 32) { throw [System.ArgumentException]"Invalid key size. Key must be 32 bytes." } $this.Key = $key } [byte[]] ComputeHash([Byte[]]$nput) { # Constants $block = $null $blockSize = 16 $blockLength = $blockSize * 4 $r = New-Object 'UInt64[]' 16 $h = New-Object 'UInt64[]' 17 # Initialize r array for ($i = 0; $i -lt 16; $i++) { if (($i * 8) + 7 -lt $this.Key.Length) { $r[$i] = [BitConverter]::ToUInt64($this.Key, ($i * 8)) } else { $r[$i] = 0 } } for ($i = 0; $i -lt 17; $i++) { $h[$i] = 0 } # Process input bytes for ($offset = 0; $offset -lt $nput.Length; $offset += $blockLength) { $block = New-Object 'Byte[]' $blockLength [Array]::Copy($nput, $offset, $block, 0, [Math]::Min($blockLength, $nput.Length - $offset)) ($h, $r, $block) = $this.Poly1305_Block($h, $r, $block) } # Finalize [UInt64]$s = 0 for ($i = 0; $i -lt 16; $i++) { $s = ($s + $h[$i]) -band 0xffffffff if ($i -eq 15) { break } $s = ($s -shr 26) -band 0xffffffff } $mac = [BitConverter]::GetBytes($s) [Array]::Reverse($mac) for ($i = 1; $i -lt 16; $i++) { [UInt64]$c = $h[$i] + 16 - $mac[$i - 1] $mac = $mac + [BitConverter]::GetBytes($c) [Array]::Reverse($mac[($mac.Length - 8)..($mac.Length - 1)]) } return $mac[0..15] } [array] Poly1305_Block([UInt64[]]$h, [UInt64[]]$r, [UInt64[]]$m) { [UInt64[]]$h = [bitwUtil]::Reduce($h) [UInt64[]]$r = [bitwUtil]::Reduce($r) [UInt64[]]$m = [bitwUtil]::Reduce($m) [UInt64] $s = 0 [UInt64] $d0 = 0 [UInt64] $d1 = 0 for ($i = 0; $i -lt 16; $i++) { $d0 = ($m[$i] + $h[$i]) -band 0xffffffff $d1 = ($d0 * $r[0]) -band 0xffffffff $d0 = ($d0 + ((($d1 * $r[1]) -band 0xffffffff) -shl 16)) -band 0xffffffff $d0 = ($d0 % 130) -band 0xffffffff $s = ($s + $d0) -band 0xffffffff if ($i -eq 15) { break } $s = ($s * 5) -band 0xffffffff $s = ($s + 0x800000000000) -band 0xffffffff $s = ($s % 130) -band 0xffffffff } $h[0] = $s -band 0xffffffff for ($i = 1; $i -lt 17; $i++) { $s = ($s -shr 26) -band 0xffffffff $h[$i] = ($h[$i] + $s) -band 0xffffffff } return ($h, $r, $m) } } #endregion Poly1305 #endregion Custom_EncClasses_&_Helpers #region FileCrypter # .SYNOPSIS # Simple file encryption & some script obfuscation # .NOTES # - Requires powershell core # - Obfuscation method was inspired by: https://netsec.expert/posts/write-a-crypter-in-any-language # I modified the functions from netsec blog and tried my best to avoid using Invoke-Expression in any possible way, since its like a red flag for Anti viruses. I Instead used "& ([scriptblock]::Create(...." Class FileCryptr { static hidden [System.Object] $CommonFileExtensions; static hidden [ValidateNotNullOrEmpty()][string] $_salt; static hidden [ValidateNotNullOrEmpty()][securestring] $Password; static hidden [ValidateNotNullOrEmpty()][Compression] $Compression; FileCryptr() { [FileCryptr]::_salt = 'bz07LmY5XiNkXW1WQjxdXw==' [FileCryptr]::Compression = [Compression]::Gzip [FileCryptr]::CommonFileExtensions = New-Object System.Management.Automation.PSStyle+FileInfoFormatting+FileExtensionDictionary } static [void] Encrypt([string]$FilePath) { [FileCryptr]::Encrypt($FilePath, 1) } static [void] Encrypt([string]$FilePath, [string]$Outfile) { [FileCryptr]::Encrypt($FilePath, 1, $Outfile) } static [void] Encrypt([string]$FilePath, [int]$iterations) { [FileCryptr]::Encrypt($FilePath, $iterations, $FilePath) } static [void] Encrypt([string]$FilePath, [int]$iterations, [string]$Outfile) { [FileCryptr]::Password = [System.Text.Encoding]::UTF8.GetString( [System.Security.Cryptography.Rfc2898DeriveBytes]::new( [xcrypt]::GetUniqueMachineId(), [convert]::FromBase64String([FileCryptr]::_salt), 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1 ).GetBytes(256 / 8) ) | xconvert ToSecurestring [FileCryptr]::Encrypt($FilePath, [FileCryptr]::Password, $iterations, $Outfile) } static [void] Encrypt([string]$FilePath, [securestring]$passwr0d, [int]$iterations) { [FileCryptr]::Encrypt($FilePath, $passwr0d, $iterations, $FilePath) } static [void] Encrypt([string]$FilePath, [securestring]$passwr0d, [int]$iterations, [string]$Outfile) { [byte[]]$clearData = [System.IO.File]::ReadAllBytes($FilePath); $Outfile = [xcrypt]::GetUnResolvedPath($Outfile); if (![IO.File]::Exists($Outfile)) { New-Item -Path $Outfile -ItemType File } $encryptedBytes = [AesGCM]::Encrypt($clearData, $passwr0d, [convert]::FromBase64String([FileCryptr]::_salt), $iterations) [System.IO.File]::WriteAllBytes($Outfile, $encryptedBytes) } static [void] Decrypt([string]$FilePath) { [FileCryptr]::Decrypt($FilePath, 1) } static [void] Decrypt([string]$FilePath, [string]$Outfile) { [FileCryptr]::Decrypt($FilePath, 1, $Outfile) } static [void] Decrypt([string]$FilePath, [int]$iterations) { [FileCryptr]::Decrypt($FilePath, $iterations, $FilePath) } static [void] Decrypt([string]$FilePath, [int]$iterations, [string]$Outfile) { [FileCryptr]::Password = [System.Text.Encoding]::UTF8.GetString( [System.Security.Cryptography.Rfc2898DeriveBytes]::new( [xcrypt]::GetUniqueMachineId(), [convert]::FromBase64String([FileCryptr]::_salt), 1000, [System.Security.Cryptography.HashAlgorithmName]::SHA1 ).GetBytes(256 / 8) ) | xconvert ToSecurestring [FileCryptr]::Decrypt($FilePath, [FileCryptr]::Password, $iterations, $Outfile) } static [void] Decrypt([string]$FilePath, [securestring]$passwr0d, [int]$iterations) { [FileCryptr]::Decrypt($FilePath, $passwr0d, $iterations, $FilePath) } static [void] Decrypt([string]$FilePath, [securestring]$passwr0d, [int]$iterations, [string]$Outfile) { [byte[]]$encryptedData = [System.IO.File]::ReadAllBytes($FilePath); $Outfile = [xcrypt]::GetUnResolvedPath($Outfile); if (![IO.File]::Exists($Outfile)) { New-Item -Path $Outfile -ItemType File } $decryptedBytes = [AesGCM]::Decrypt($encryptedData, $passwr0d, [convert]::FromBase64String([FileCryptr]::_salt), $iterations) [System.IO.File]::WriteAllBytes($Outfile, $decryptedBytes) } static [string] GetStub([string]$filePath) { [int]$keySize = 256; [byte[]]$salt = [convert]::FromBase64String([FileCryptr]::_salt); [string]$b64k = [convert]::ToBase64String( [System.Security.Cryptography.Rfc2898DeriveBytes]::new( [FileCryptr]::RNdvar(), $salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1 ).GetBytes($keySize / 8) ); return [FileCryptr]::GetStub($filePath, $b64k); } static [string] RNdvar() { return [FileCryptr]::RNdvar(15) } static [string] RNdvar([string]$Length) { return [Guid]::NewGuid().Guid.subString($Length).replace('-', [string]::Join('', (0..9 | Get-Random -Count 1))) } static [string] GetStub([string]$filePath, [string]$base64key) { return [FileCryptr]::GetStub($filePath, $base64key, [FileCryptr]::_salt) } static hidden [string] GetStub([string]$filePath, [string]$base64key, [string]$salt) { Write-Verbose "[+] Reading file: '$($filePath)' ..." $filePath = [xcrypt]::GetUnResolvedPath($filePath) if (![IO.File]::Exists($filePath)) { throw [System.IO.FileNotFoundException]::new("Unable to find the file: $filePath") } $codebytes = [System.Text.Encoding]::UTF8.GetBytes([System.IO.File]::ReadAllText($filePath, [System.Text.Encoding]::UTF8)); $Comprssnm = [FileCryptr]::Compression.ToString() $EncrBytes = $null Write-Verbose "[+] Encrypting ...$( $Passw = [SecureString]($base64key | xconvert ToSecurestring) $bSalt = [byte[]][Convert]::FromBase64String($salt); [int]$KeySize = 256; $CryptoProvider = $null; if ($Comprssnm -notin ([Enum]::GetNames('Compression' -as 'Type'))) { Throw [System.InvalidCastException]::new("The name '$Comprssnm' is not a valid [Compression]`$typeName.") }; Set-Variable -Name CryptoProvider -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.AesCryptoServiceProvider]::new()); $CryptoProvider.KeySize = [int]$KeySize; $CryptoProvider.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7; $CryptoProvider.Mode = [System.Security.Cryptography.CipherMode]::CBC; $CryptoProvider.Key = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($Passw | xconvert ToString), $bSalt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes($KeySize / 8); $CryptoProvider.IV = [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($passw | xconvert ToString), $bsalt, 1, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(16); Set-Variable -Name EncrBytes -Scope Local -Visibility Private -Option Private -Value $($CryptoProvider.IV + $CryptoProvider.CreateEncryptor().TransformFinalBlock($codebytes, 0, $codebytes.Length)); Set-Variable -Name EncrBytes -Scope Local -Visibility Private -Option Private -Value $(Compress-Data -b $EncrBytes -c $Comprssnm)); $CryptoProvider.Clear(); $CryptoProvider.Dispose() ) Done." $base64encString = [convert]::ToBase64String($EncrBytes) $base64encbArray = $base64encString.ToCharArray(); [array]::Reverse($base64encbArray); $base64encrevstr = -join $base64encbArray [string]$xorPassword = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([xcrypt]::GeneratePassword(30, $true, $false, $true, $true))) [byte[]]$Passwdbytes = [System.Text.Encoding]::UTF8.GetBytes($xorPassword); $xkey64 = [convert]::ToBase64String($Passwdbytes) $base64XOREncPayload = [Convert]::ToBase64String([xor]::Encrypt([System.Text.Encoding]::UTF8.GetBytes($base64encrevstr), $Passwdbytes, 1)) Write-Verbose "[+] Finalizing Code Layer ..." $s = [string]::Empty $l = @(); $n = "`r`n" $l += '${9} = [int][System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String("MjU2"))' + $n $l += '${7} = [System.Convert]::FromBase64String("LkNyZWF0ZURlY3J5cHRvcigpLlRyYW5zZm9ybUZpbmFsQmxvY2s=")' + $n $l += '${2} = [System.Convert]::FromBase64String("{25}")' + $n $l += '${3} = [System.Convert]::FromBase64String("{0}")' + $n $l += '${4} = [byte[]]$(for (${6} = 0; ${6} -lt ${3}.length) {' + $n $l += ' for (${5} = 0; ${5} -lt ${2}.length; ${5}++) {' + $n $l += ' ${3}[${6}] -bxor ${2}[${5}]' + $n $l += ' ${6}++' + $n $l += ' if (${6} -ge ${3}.Length) {' + $n $l += ' ${5} = ${2}.length' + $n $l += ' }' + $n $l += ' }' + $n $l += ' }' + $n $l += ')' + $n $s += $l -join ''; $l = @() $l += '[array]::Reverse(${4});' + $n $l += '${19} = -join [char[]]${4};' + $n $l += '${20} = $null; ${11} = $null; ${12} = $null' + $n $l += '${10} = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("RGVjb21wcmVzcw=="));' + $n $l += '${21} = & ([scriptblock]::Create("$([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W1N5c3RlbS5JTy5NZW1vcnlTdHJlYW1dOjpuZXc=")))([System.Convert]::FromBase64String(`"${19}`"))"));' + $n $l += '${13} = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W0lPLkNvbXByZXNzaW9uLkNvbXByZXNzaW9uTW9kZV0="))));' + $n $l += '${14} = switch ("{23}") {' + $n $l += ' "Gzip" { New-Object ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("U3lzdGVtLklPLkNvbXByZXNzaW9uLkd6aXBTdHJlYW0="))) ${21}, (${13}::${10}) }' + $n $l += ' "Deflate" { New-Object ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("U3lzdGVtLklPLkNvbXByZXNzaW9uLkRlZmxhdGVTdHJlYW0="))) ${21}, (${13}::${10}) }' + $n $l += ' "ZLib" { New-Object (([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("U3lzdGVtLklPLkNvbXByZXNzaW9uLlpMaWJTdHJlYW0=")))) ${21}, (${13}::${10}) }' + $n $l += ' Default { throw ([System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String("RmFpbGVkIHRvIERlQ29tcHJlc3MgQnl0ZXMuIENvdWxkIE5vdCByZXNvbHZlIENvbXByZXNzaW9uIQ=="))) }' + $n $l += '}' + $n $s += $l -join ''; $l = @() $l += '${15} = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W1N5c3RlbS5JTy5NZW1vcnlTdHJlYW1dOjpOZXcoKQ=="))))' + $n $l += '[void]${14}.CopyTo(${15}); ${14}.Close(); ${14}.Dispose(); ${21}.Close();' + $n $l += '[byte[]]${12} = ${15}.ToArray(); ${15}.Close();' + $n $l += 'Set-Variable -Name {20} -Scope Local -Visibility Private -Option Private -Value (& ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W1N5c3RlbS5TZWN1cml0eS5DcnlwdG9ncmFwaHkuQWVzQ3J5cHRvU2VydmljZVByb3ZpZGVyXTo6bmV3KCk=")))));' + $n $l += '${18} = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W1N5c3RlbS5TZWN1cml0eS5DcnlwdG9ncmFwaHkuQ2lwaGVyTW9kZV06OkNCQw=="))))' + $n $l += '${17} = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W1N5c3RlbS5TZWN1cml0eS5DcnlwdG9ncmFwaHkuUGFkZGluZ01vZGVdOjpQS0NTNw=="))))' + $n $l += '${16} = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("W1N5c3RlbS5TZWN1cml0eS5DcnlwdG9ncmFwaHkuSGFzaEFsZ29yaXRobU5hbWVdOjpTSEEx"))))' + $n $l += '${8} = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String("W1N5c3RlbS5TZWN1cml0eS5DcnlwdG9ncmFwaHkuUmZjMjg5OERlcml2ZUJ5dGVzXTo6bmV3")) + "(`"{1}`", [Convert]::FromBase64String(`"{24}`"), 10000, `"${16}`")"))' + $n $l += '${20}.KeySize = ${9};' + $n $l += '${20}.Padding = ${17}' + $n $l += '${20}.Mode = ${18}' + $n $l += '${20}.Key = ${8}.GetBytes(${9} / 8)' + $n $l += '${20}.IV = ${12}[0..15];' + $n $l += 'Set-Variable -Name {11} -Scope Local -Visibility Private -Option Private -Value $(${20}.CreateDecryptor().TransformFinalBlock(${12}, 16, ${12}.Length - 16))' + $n $l += '${20}.Clear(); ${20}.Dispose();' + $n $l += '${7} = [System.Text.Encoding]::UTF8.GetString(${11});' + $n if ([IO.FileInfo]::new($filePath).Extension -in ('.ps1', '.psm1', '.cmd', '.bat', '.sh')) { # Why only these Extension? Well I mainly wrote this crypter to hide scripts from WDefender. $l += '& ([scriptblock]::Create("${7}"));' + $n } else { $l += 'echo ${7}' + $n } $s += $l -join '' $s = $s.Replace("{0}", $base64XOREncPayload) $s = $s.Replace("{1}", $base64key) $s = $s.Replace("{25}", $xkey64) $s = $s.Replace("{23}", $Comprssnm) $s = $s.Replace("{24}", $salt) 2..21 | ForEach-Object { $s = $s.Replace("{$_}", [FileCryptr]::RNdvar()) } return $s } static [string] Obfuscate([string]$filePath) { return [FileCryptr]::Obfuscate($filePath, $filePath) } static [string] Obfuscate([string]$filePath, [string]$OutputFile) { if (![FileCryptr]::IsTextFile($filePath)) { throw [Exception]::new("Error: $filePath is not a text file.") } $stub = [FileCryptr]::GetStub($filePath) $OutputFile = [xcrypt]::GetUnResolvedPath($OutputFile) return (Set-Content -Path $OutputFile -Value $stub -Encoding UTF8 -PassThru) } static [string] Deobfuscate([string]$base64EncodedString) { throw 'Idk! Just run the damn file; (and hope its not a virus).' } static [bool] IsTextFile([string]$filePath) { @(# Default file extensions to speed up the detection process. ".txt", ".log", ".ini", ".env", ".cfg", ".conf", ".cnf", ".properties", ".props", ".prop", ".rtf", ".csv", ".jsx", ".tsv", ".ssv", ".dsv", ".csv", ".tab", ".vcf", ".js", ".json", ".py", ".pl", ".pm", ".t", ".php", ".php3", ".php4", ".php5", ".phtml", ".inc", ".phps", ".asp", ".aspx", ".asax", ".ascx", ".ashx", ".asmx", ".css", ".html", ".htm", ".shtml", ".xhtml", ".md", ".markdown", ".mdown", ".mkd", ".rst", ".xml", ".yml", ".ps1", ".psm1", ".psd1", ".pssc", ".cdxml", ".clixml", ".xaml", ".toml", ".resx", ".restext", ".unity", ".sln", ".csproj", ".vbproj", ".vcxproj", ".vcxproj.filters", ".proj", ".projitems", ".shproj", ".scc", ".suo", ".sln", ".cs", ".vb", ".vc", ".vcx", ".cxx", ".cpp", ".h", ".hpp", ".hh", ".hxx", ".inc", ".inl", ".ipp", ".tcc", ".tpp", ".cc", ".c", ".mm", ".m", ".s", ".sx", ".S", ".rs", ".rlib", ".def", ".odl", ".idl", ".odl", ".hdl", ".vhd", ".vhdl", ".ucf", ".qsf", ".tcl", ".tk", ".itk", ".tkm", ".blt", ".tcl" ) | ForEach-Object { [FileCryptr]::CommonFileExtensions.Add($_, '') } if ([FileCryptr]::CommonFileExtensions.ContainsKey([IO.Path]::GetExtension($filePath))) { return $true; } try { $null = [IO.File]::ReadAllText($filePath); return $true; } catch [Exception] { return $false; } } } #endregion FileCrypter #region Custom_Cryptography_Wrappers class k3y { [ValidateNotNullOrEmpty()][CredManaged]$User; [ValidateNotNullOrEmpty()][Expiration]$Expiration; [ValidateNotNullOrEmpty()][securestring]hidden $UID; [ValidateNotNullOrEmpty()][int]hidden $_PID = [System.Environment]::ProcessId; [ValidateNotNullOrEmpty()][keyStoreMode]hidden $Storage = [KeyStoreMode]::Securestring; [ValidateNotNullOrEmpty()][version]hidden $version = [version]::new("1.0.0.1"); [ValidateNotNullOrEmpty()][byte[]] static hidden $Salt = [System.Text.Encoding]::UTF7.GetBytes('hR#ho"rK6FMu mdZFXp}JMY\?NC]9(.:6;>oB5U>.GkYC-JD;@;XRgXBgsEi|%MqU>_+w/RpUJ}Kt.>vWr[WZ;[e8GM@P@YKuT947Z-]ho>E2"c6H%_L2A:O5:E)6Fv^uVE; aN\4t\|(*;rPRndSOS(7& xXLRKX)VL\/+ZB4q.iY { %Ko^<!sW9n@r8ihj*=T $+Cca-Nvv#JnaZh'); #this is the default salt, change it if you want. k3y([string]$Passw0rd) { $Password = $Passw0rd | xconvert ToSecurestring $this.User = [CredManaged]::new([pscredential]::new($(whoami), $Password)); $this.Expiration = [expiration]::new(0, 1); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([securestring]$Password) { $this.User = [CredManaged]::new([pscredential]::new($(whoami), $Password)); $this.Expiration = [expiration]::new(0, 1); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([CredManaged]$User, [Datetime]$Expiration) { $this.User = $User $this.Expiration = [expiration]::new($Expiration); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([securestring]$Password, [Datetime]$Expiration) { $this.User = [CredManaged]::new([pscredential]::new($(whoami), $Password)); $this.Expiration = [Expiration]::new($Expiration); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([securestring]$Password, [byte[]]$salt, [Datetime]$Expiration) { $this.User = [CredManaged]::new([pscredential]::new($(whoami), $Password)); [k3y]::Salt = $salt; $this.Expiration = [Expiration]::new($Expiration); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([pscredential]$User, [Datetime]$Expiration) { ($this.User, $this.Expiration) = ([CredManaged]::new($User), [Expiration]::new($Expiration)); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([string]$UserName, [securestring]$Password) { $this.User = [CredManaged]::new([pscredential]::new($UserName, $Password)); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } k3y([string]$UserName, [securestring]$Password, [Datetime]$Expiration) { ($this.User, $this.Expiration) = ([CredManaged]::new([pscredential]::new($UserName, $Password)), [Expiration]::new($Expiration)); $this.SetUID(); $this.PsObject.Methods.Remove('SetUID'); } [void]hidden SetUID() { $assocdataProps = ($this | Get-Member -Force | Where-Object { $_.MemberType -eq "Property" -and $_.Name -notin ('UID', 'Salt') }) | Select-Object -ExpandProperty Name $s = [string]::Empty $s += '"' + ($assocdataProps -join '","') + '"' + "`n"; $vals = $assocdataProps | ForEach-Object { $this.$_.Tostring() }; $fs = '"{' + ((0 .. ($vals.Count - 1)) -join '}","{') + '}"'; $s += $fs -f $vals; $O = ConvertFrom-Csv $s; $O.User = $this.User $op = ($o.User | Get-Member -Force | Where-Object { $_.MemberType -eq "Property" }) | Select-Object -ExpandProperty Name $st = ("[PSCustomObject]@{`n " + $($op | ForEach-Object { "$_ = '$($O.User.$_)'`n" }) + '}').Replace(" Password = 'System.Security.SecureString'", " password = '$(($O.User.password | xconvert ToString))'") $O.User = [convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($st)); $s = $(ConvertTo-Csv $O).Split('"Expiration","Storage","User","version","_PID"', [System.StringSplitOptions]::TrimEntries); $dt = [System.Text.Encoding]::UTF8.GetBytes($s) $ps = $null; Set-Variable -Name ps -Scope Local -Visibility Private -Option Private -Value $(( [convert]::ToBase64String( [System.Security.Cryptography.Rfc2898DeriveBytes]::new(($this.User.Password | xconvert ToString), [k3y]::Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(256 / 8) ) ) | xconvert ToSecurestring ); Write-Verbose "[+] Res-password $($ps | xconvert ToString)" Write-Verbose "[+] Res-password Length: $([convert]::FromBase64String(($ps | xconvert ToString)).Length)" & ([scriptblock]::Create("`$this.psobject.Properties.Add([psscriptproperty]::new('UID', { ConvertTo-SecureString -AsPlainText -String '$([convert]::ToBase64String([Shuffl3r]::Combine([AesGcm]::Encrypt($dt, $ps, [k3y]::Salt), [convert]::FromBase64String(($ps | xconvert ToString)), [xcrypt]::GetUniqueMachineId())))' -Force }))")); } [psobject] GetInfo([string]$passw0rd) { return $this.GetInfo(($passw0rd | xconvert ToSecurestring)) } [psobject] GetInfo([securestring]$password) { return $this.GetInfo(($this.UID | xconvert ToString), $password, [k3y]::Salt) } [psobject] GetInfo([string]$UID, [securestring]$password, [byte[]]$salt) { return [System.Text.Encoding]::UTF8.GetString([AesGcm]::Decrypt([System.Convert]::FromBase64String($UID), $this.ResolvePassword($Password), $salt)) | ConvertFrom-Csv } [bool] IsValid() { return $this.IsValid($false) } [bool] IsValid([bool]$ThrowOnFailure) { # Verifies if The password has already been set. $IsValid = $false; [bool]$SetValu3Exception = $false; [securestring]$kUID = $this.UID; $InnerException = [System.Exception]::new() try { $this.UID = [securestring]::new() } catch [System.Management.Automation.SetValueException] { $SetValu3Exception = $true } catch { $InnerException = $_.Exception } finally { if ($SetValu3Exception) { $IsValid = $true } else { $this.UID = $kUID } } if ($ThrowOnFailure -and !$IsValid) { throw [System.InvalidOperationException]::new("The key Hasn't been used!`nEncrypt Something with this K3Y at least once or Manually Call SetK3YUID method.", $InnerException) } return $IsValid } # [securestring] hidden ResolvePassword([securestring]$Password) { # if (!$this.IsHashed()) { # $hashSTR = [string]::Empty; Set-Variable -Name hashSTR -Scope local -Visibility Private -Option Private -Value $([string]([HKDF2]::GetToken($password) | xconvert ToHexString)); # & ([scriptblock]::Create("`$this.User.psobject.Properties.Add([psscriptproperty]::new('Password', { ConvertTo-SecureString -AsPlainText -String '$hashSTR' -Force }))")); # } # $SecHash = $this.User.Password; # return [ArgonCage]::Resolve($Password, $SecHash) # } [bool]IsHashed() { return $this.IsHashed($false); } static [bool] IsHashed([K3Y]$k3y) { $ThrowOnFailure = $false return [K3Y]::IsHashed($k3y, $ThrowOnFailure); } [bool]IsHashed([bool]$ThrowOnFailure) { return [K3Y]::IsHashed($this, $ThrowOnFailure); } static [bool] IsHashed([K3Y]$k3y, [bool]$ThrowOnFailure) { # Verifies if The password (the one only you know) has already been hashed [bool]$SetValu3Exception = $false; [securestring]$p = $k3y.User.Password; $InnerException = [System.Exception]::new() [bool]$IsHashed = [regex]::IsMatch([string]($k3y.User.Password | xconvert ToString), "^[A-Fa-f0-9]{72}$"); try { $k3y.User.Password = [securestring]::new() # This will not work if the hash has been set } catch [System.Management.Automation.SetValueException] { $SetValu3Exception = $true } catch { $InnerException = $_.Exception } finally { $IsHashed = $IsHashed -and $SetValu3Exception } if (!$SetValu3Exception) { $k3y.User.Password = $p } if ($ThrowOnFailure -and !$IsHashed) { throw [System.InvalidOperationException]::new('Operation is not valid due to the current state of the object. No password Hash found.', $InnerException) } return $IsHashed } [void]Export([string]$FilePath) { $this.Export($FilePath, $false); } [void]Export([string]$FilePath, [bool]$encrypt) { $ThrowOnFailure = $true; [void]$this.IsValid($ThrowOnFailure) $FilePath = [xcrypt]::GetUnResolvedPath($FilePath) if (![IO.File]::Exists($FilePath)) { New-Item -Path $FilePath -ItemType File | Out-Null } Set-Content -Path $FilePath -Value ($this.Tostring()) -Encoding UTF8 -NoNewline; if ($encrypt) { Write-Verbose "[i] Export encrypted key to $FilePath"; [Filecryptr]::Encrypt($FilePath) }; } static [K3Y] Create([System.IO.FileInfo]$File) { $tmp = [IO.Path]::GetTempFileName() [Filecryptr]::Decrypt($File.FullName, $tmp) $_id = Get-Content $tmp; Remove-Item $tmp return [K3Y]::Create($_id) } static [K3Y] Create([string]$uid) { ($idb, $pb) = [Shuffl3r]::Split([convert]::FromBase64String($uid), [xcrypt]::GetUniqueMachineId(), 32) $dec = [AesGcm]::Decrypt($idb, ([convert]::ToBase64String($pb) | xconvert ToSecurestring), [k3y]::Salt) $Obj = ConvertFrom-Csv $('"Expiration","Storage","User","version","_PID"' + "`n" + ([System.Text.Encoding]::UTF8.GetString($dec).Trim())) $Obj.User = & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String($Obj.User)))); $Obj.user.password = ($Obj.user.password | xconvert ToSecurestring) $Obj.User = & { $usr = [credmanaged]::new(); $usr.psobject.properties.name | ForEach-Object { $usr.$_ = $Obj.User.$_ }; $usr } Write-Verbose "[i] Create new k3y object ..." $K3Y = [K3Y]::new($Obj.User, ($Obj.Expiration | xconvert ToDateTime)) $K3Y._PID = $Obj._PID; $K3Y.version = [version]$Obj.version; $K3Y.Storage = [keyStoreMode]$Obj.Storage return $K3Y } [K3Y]Import([string]$uid) { $K3Y = $null; Set-Variable -Name K3Y -Scope Local -Visibility Private -Option Private -Value ([K3Y]::Create($uid)); try { $this | Get-Member -Force | Where-Object { $_.Membertype -eq 'property' } | ForEach-Object { $this.$($_.Name) = $K3Y.$($_.Name) }; } catch [System.Management.Automation.SetValueException] { throw [System.InvalidOperationException]::New('You can only Import One Key.') } $Key_UID = [string]::Empty; $hashSTR = [string]::Empty; Set-Variable -Name hashSTR -Scope local -Visibility Private -Option Private -Value $([string]($this.User.Password | xconvert ToString)); if ([regex]::IsMatch($hashSTR, "^[A-Fa-f0-9]{72}$")) { & ([scriptblock]::Create("`$this.User.psobject.Properties.Add([psscriptproperty]::new('Password', { ConvertTo-SecureString -AsPlainText -String '$hashSTR' -Force }))")) } Set-Variable -Name Key_UID -Scope local -Visibility Private -Option Private -Value $([string]($K3Y.UID | xconvert ToString)) & ([scriptblock]::Create("`$this.psobject.Properties.Add([psscriptproperty]::new('UID', { ConvertTo-SecureString -AsPlainText -String '$Key_UID' -Force }))")); return $K3Y } [void]SetPassword([securestring]$password) {} [void]SaveToVault() { $_Hash = $this.User.Password | xconvert ToString; if ([string]::IsNullOrWhiteSpace($_Hash)) { throw 'Please set a Password first.' } $RName = 'PNKey' + $_Hash $_Cred = New-Object -TypeName CredManaged -ArgumentList ($RName, $this.User.UserName, ($this | xconvert ToString)) Write-Verbose "[i] Saving $RName To Vault .." # Note: Make sure file size does not exceed the limit allowed and cannot be saved. $_Cred.SaveToVault() } [string]Tostring() { return ($this.UID | xconvert ToString) } } #region _The_K3Y # The K3Y 'UID' [ see .SetK3YUID() method ] is a fancy way of storing the version, user/owner credentials, Compression alg~tm used and Other Info # about the most recent use and the person who used it; so it can be analyzed later to verify some rules before being used again. This enables the creation of complex expiring encryptions. # It does not store or use the actual password; instead, it employs its own 'KDF' and retains a 'SHA1' hash string as securestring objects. idk if this is the most secure way to use but it should work. # [byte[]]Encrypt([byte[]]$BytesToEncrypt, [securestring]$Password, [byte[]]$salt, [string]$Compression, [Datetime]$Expiration, [CryptoAlgorithm]$Algorithm) { # $Password = [securestring]$this.ResolvePassword($Password); $this.SetK3YUID($Password, $Expiration, $Compression, $this._PID) # # $CryptoServiceProvider = [CustomCryptoServiceProvider]::new($bytesToEncrypt, $Password, $salt, [CryptoAlgorithm]$Algorithm) # # $encryptor = $CryptoServiceProvider.CreateEncryptor(); $result = $encryptor.encrypt(); # return [AesGCM]::Encrypt($bytesToEncrypt, $Password, $salt); # } # [byte[]]Decrypt([byte[]]$BytesToDecrypt, [securestring]$Password, [byte[]]$salt) { # $Password = [securestring]$this.ResolvePassword($Password); # (Get The real Password) # ($IsValid, $Compression) = [k3Y]::AnalyseK3YUID($this, $Password, $false)[0, 2]; # if (!$IsValid) { throw [System.Management.Automation.PSInvalidOperationException]::new("The Operation is not valid due to Expired K3Y.") }; # if ($Compression.Equals('')) { throw [System.Management.Automation.PSInvalidOperationException]::new("The Operation is not valid due to Invalid Compression.", [System.ArgumentNullException]::new('Compression')) }; # # todo: Chose the algorithm # # if alg -eq RSA then we RSA+AES hybrid # return [AesGCM]::Decrypt($bytesToDecrypt, $Password, $salt); # # $Compression # } class KeyManager { KeyManager() {} } #endregion _The_K3Y #region Custom_Encryptor # .DESCRIPTION # A custom encryptor to make it easy to combine several algorithms. # .EXAMPLE # $encryptor = [Encryptor]::new($bytesToEncrypt, [securestring]$Password, [byte]$salt, [CryptoAlgorithm]$Algorithm); # $encrypted = $encryptor.encrypt(); class Encryptor { Encryptor([byte[]]$BytesToEncrypt, [securestring]$Password, [byte[]]$salt, [CryptoAlgorithm]$Algorithm) { $this.bytes = $bytesToEncrypt $this.Password = $Password $this.salt = $salt $this.setAlgorithm($Algorithm) } Encryptor([byte[]]$bytesToEncrypt, [K3Y]$key) { $this.bytes = $bytesToEncrypt # Dynamicaly create methods using [PsScriptMethod] : https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.psscriptmethod # $key.PSObject.Methods.Add( # [psscriptmethod]::new( # 'MethodName', { # param() # return 'stuff' # } # ) # ) } [void]setAlgorithm([string]$Algorithm) {} [void]setAlgorithm([CryptoAlgorithm]$Algorithm) {} [byte[]] Encrypt() { $Encrypted = $null switch ([string]$this.Algorithm) { 'AesGCM' { # {aes aesgcm} encrypt using: $this.bytes; $this.Password; $this.salt; $this.Algorithm $Encrypted = 'bytes encrypted Using aes aesgcm' } 'ChaCha20' { $Encrypted = 'bytes encrypted Using ChaCha20 + SHA256' } 'RsaAesHMAC' { # {aes + rsa} encrypt using: $this.bytes; $this.Password; $this.salt; $this.Algorithm # # Generate a random AES key and initialization vector # $aesKey = New-Object Byte[] 32 # [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($aesKey) # $aesIV = New-Object Byte[] 16 # [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($aesIV) # # Create an RSA key for encryption and decryption # $rsa = New-Object Security.Cryptography.RSACryptoServiceProvider # $rsaPublicKey = $rsa.ExportParameters(false) # $rsaPrivateKey = $rsa.ExportParameters(true) # # Encrypt the AES key using RSA encryption # $encryptedAesKey = $rsa.Encrypt($aesKey, $false) # # Create a HMACSHA256 object and compute the HMAC of the original data # $hmac = New-Object System.Security.Cryptography.HMACSHA256 # $hmac.Key = $aesKey # $dataHMAC = $hmac.ComputeHash($bytes) # # Encrypt the data using AES encryption # $aes = New-Object System.Security.Cryptography.AesCryptoServiceProvider # $aes.Key = $aesKey # $aes.IV = $aesIV # $encryptor = $aes.CreateEncryptor() # $encryptedBytes = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length) # # Combine the encrypted data, HMAC, and encrypted AES key into a single encrypted payload # $encryptedPayload = [Byte[]]::new($encryptedBytes.Length + $dataHMAC.Length + $encryptedAesKey.Length) # $encryptedBytes.CopyTo($encryptedPayload, 0) # $dataHMAC.CopyTo($encryptedPayload, $encryptedBytes.Length) # $encryptedAesKey.CopyTo($encryptedPayload, $encryptedBytes.Length + $dataHMAC.Length) # return $encryptedPayload $Encrypted = 'bytes encrypted Using RsaAesHMAC' } 'RsaECDSA' { # RSA-ECDSA: RSA and ECDSA (Elliptic Curve Digital Signature Algorithm) are public-key cryptography algorithms that are often used together. RSA can be used for encrypting data, while ECDSA can be used for digital signatures, providing both confidentiality and authenticity for the data. # {RSA + ECDSA} encrypt using: $this.bytes; $this.Password; $this.salt; $this.Algorithm # Create an RSA key for encryption and decryption # $rsa = New-Object Security.Cryptography.RSACryptoServiceProvider # $rsaPublicKey = $rsa.ExportParameters(false) # $rsaPrivateKey = $rsa.ExportParameters(true) # # Create an ECDSA key for signing and verifying # $ecdsa = New-Object Security.Cryptography.ECDsaCng # $ecdsaPublicKey = $ecdsa.Key.Export(Security.Cryptography.CngKeyBlobFormat::GenericPublicBlob) # $ecdsaPrivateKey = $ecdsa.Key.Export(Security.Cryptography.CngKeyBlobFormat::GenericPrivateBlob) # # Encrypt the data using RSA encryption # $encryptedBytes = $rsa.Encrypt($bytes, $false) # # Sign the encrypted data using ECDSA # $signature = $ecdsa.SignData($encryptedBytes) # # Combine the encrypted data and signature into a single encrypted payload # $encryptedPayload = [Byte[]]::new($encryptedBytes.Length + $signature.Length) # $encryptedBytes.CopyTo($encryptedPayload, 0) # $signature.CopyTo($encryptedPayload, $encryptedBytes.Length) $Encrypted = 'bytes encrypted Using RSA-ECDSA' } 'RsaOAEP' { # Generate a new RSA key pair # $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider # $publicKey = $rsa.ExportParameters(False) # $privateKey = $rsa.ExportParameters(True) # # Encrypt the data using RSA-OAEP # $rsaEncryptor = New-Object System.Security.Cryptography.RSAPKCS1KeyExchangeFormatter($publicKey) # $encryptedBytes = $rsaEncryptor.Encrypt([System.Text.Encoding]::UTF8.GetBytes("secret data"), "OAEP") $Encrypted = 'bytes encrypted Using RsaOAEP' } Default { throw "Please Provide a valid algorithm" } } return $Encrypted } } # .DESCRIPTION # A custom decryptor. # .EXAMPLE # $decryptor = [Decryptor]::new($bytesToDecrypt, [securestring]$Password, [byte]$salt, [CryptoAlgorithm]$Algorithm); # $decrypted = $Decryptor.encrypt(); class Decryptor { Decryptor([byte[]]$BytesToDecrypt, [securestring]$Password, [byte[]]$salt, [CryptoAlgorithm]$Algorithm) { $this.bytes = $bytesToDecrypt $this.Password = $Password $this.salt = $salt $this.setAlgorithm($Algorithm) } [void]setAlgorithm([string]$Algorithm) {} [void]setAlgorithm([CryptoAlgorithm]$Algorithm) {} [byte[]]Decrypt() { $Decrypted = $null switch ([string]$this.Algorithm) { 'AesGCM' { # {aesgcm} decrypt using: $this.bytes; $this.Password; $this.salt; $this.Algorithm $Decrypted = 'bytes decrypted Using aesgcm' } 'ChaCha20' { $Decrypted = 'bytes decrypted Using ChaCha20 + SHA256' } 'RsaAesHMAC' { # {aes + rsa} decrypt using: $this.bytes; $this.Password; $this.salt; $this.Algorithm # # # Split the encrypted payload into its three components: encrypted data, HMAC, and encrypted AES key # $encryptedBytes = $encryptedPayload[0..($encryptedBytes.Length - 1)] # $dataHMAC = $encryptedPayload[$encryptedBytes.Length..($encryptedBytes.Length + $dataHMAC.Length - 1)] # $encryptedAesKey = $encryptedPayload[($encryptedBytes.Length + $dataHMAC.Length)..($encryptedPayload.Length - 1)] # # Decrypt the AES key using RSA decryption # $aesKey = $rsa.Decrypt($encryptedAesKey, $false) # # Verify the HMAC of the encrypted data # $hmac = New-Object System.Security.Cryptography.HMACSHA256 # $hmac.Key = $aesKey # $computedHMAC = $hmac.ComputeHash($encryptedBytes) # if ($computedHMAC -ne $dataHMAC) # { # throw "HMAC verification failed" # } # # Decrypt the data using AES encryption # $aes = New-Object System.Security.Cryptography.AesCryptoServiceProvider # $aes.Key = $aesKey # $aes.IV = $aesIV # $decryptor = $aes.CreateDecryptor() # $decryptedBytes = $decryptor.TransformFinalBlock($encryptedBytes, 0, $encryptedBytes.Length) # return $decryptedBytes $Decrypted = 'bytes decrypted Using aes + rsa' } 'RsaECDSA' { # {RSA + ECDSA} decrypt using: $this.bytes; $this.Password; $this.salt; $this.Algorithm # Decryption process: # # Split the encrypted payload into its two components: encrypted data and signature # $encryptedBytes = $encryptedPayload[0..($encryptedBytes.Length - 1)] # $signature = $encryptedPayload[$encryptedBytes.Length..($encryptedPayload.Length - 1)] # # Verify the signature of the encrypted data using ECDSA # if (!$ecdsa.VerifyData($encryptedBytes, $signature)) # { # throw "Signature verification failed" # } # # Decrypt the data using RSA decryption # $decryptedBytes = $rsa.Decrypt($encryptedBytes, $false) # # Return the decrypted data # return $decryptedBytes $Decrypted = 'bytes decrypted Using RsaECDSA' } 'RsaOAEP' { # # Decrypt the data using RSA-OAEP # $rsaDecryptor = New-Object System.Security.Cryptography.RSACryptoServiceProvider # $rsaDecryptor.ImportParameters($privateKey) # $decryptedBytes = $rsaDecryptor.Decrypt($encryptedBytes, "OAEP") # $decryptedMessage = [System.Text.Encoding]::UTF8.GetString($decryptedBytes) $Decrypted = 'Bytes decrypted with RSA-OAEP' # Write-Output "Decrypted message: $decryptedMessage" } Default { throw "Please Provide a valid CryptoAlgorithm" } } return $Decrypted } } #endregion Custom_Cryptography_Wrappers #region functions function Get-ObjectHelp { [CmdletBinding(DefaultParameterSetName = "Class")] param( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNull()] [PSObject]$Object, [Parameter(ParameterSetName = "Class")] [switch]$Detailed, [Parameter(ParameterSetName = "Method")] [string]$Method, [Parameter(ParameterSetName = "Property")] [string]$Property, [Parameter()] [switch]$Online ) begin { $PSCmdlet.WriteVerbose("Begin") } process { $Type = $null $TypeName = $null # $Selector = $null Write-Verbose "Start processing..." Write-Verbose ("Input object (Type:" + $Object.GetType() + ", IsType:" + ($Object -is [System.Type]) + ")") if ($Object -is [Management.Automation.PSMemberInfo]) { if ($Object -is [System.Management.Automation.PSMethod]) { $Method = $Object.Name $Type = Resolve-MemberOwnerType $Object } else { Write-Error "Unable to identify owning time of PSMembers." return } } elseif ($Object -is [Microsoft.PowerShell.Commands.MemberDefinition]) { if ($Object.MemberType -eq "Method") { $Method = $Object.Name } else { $Property = $Object.Name } if ($Object.TypeName -match '^System.Management.ManagementObject#(.+)') { $Type = $Object.TypeName } else { $Type = "$($Object.TypeName)" -as [System.Type] } } elseif ($Object -is [Microsoft.Management.Infrastructure.CimClass]) { $Type = $Object } elseif ($Object -is [Microsoft.Management.Infrastructure.CimInstance]) { $Type = $Object.PSBase.CimClass } elseif ($Object -is [System.Management.ManagementObject]) { $Type = Get-CimClass $Object.__CLASS -Namespace $Object.__NAMESPACE } elseif ($Object -is [System.__ComObject]) { $Type = $Object } elseif ($Object -is [System.String]) { switch -regex ($Object) { '^\[[^\[\]]+\]$' { ## .NET Type (ex: [System.String]) try { $Type = { $Object }.Invoke() } catch { $null } break } '^(Win32|CIM)_[\w]+' { $Type = Get-CimClass $Object } ## TODO: WMI / CIM Default {} } } elseif ($Object -as [System.Type]) { $Type = $Object -as [System.Type] } if (!$Type) { Write-Error "Could not identify object" return } Write-Verbose ("Object (Type:" + $Object.GetType() + ", IsType:" + ($Object -is [System.Type]) + ")") Write-Verbose ("Method is: $Method") Write-Verbose ("Property is: $Property") $Culture = $Host.CurrentCulture.Name ## TODO: Support culture parameter? if ($Type -is [Microsoft.Management.Infrastructure.CimClass]) { if ($Online) { if ($Uri = Get-CimUri -Type $Type -Method $Method -Property $Property) { [System.Diagnostics.Process]::Start($Uri.ToString()) | Out-Null } } else { if ($Method) { Get-CimHelp -Class $Type.CimClassName -Namespace $Type.CimSystemProperties.Namespace -Method $Method } elseif ($Property) { Get-CimHelp -Class $Type.CimClassName -Namespace $Type.CimSystemProperties.Namespace -Property $Property } else { Get-CimHelp -Class $Type.CimClassName -Namespace $Type.CimSystemProperties.Namespace -Detailed:$Detailed } } } elseif ($Type -is [System.Type]) { if ($Online) { $Member = if ($Method) { $Method } elseif ($Property) { $Property } else { $null } if ($Uri = Get-HelpUri $Type -Member $Member) { [System.Diagnostics.Process]::Start($Uri.ToString()) | Out-Null } } else { if ($Method) { Get-NetHelp -Type $Type -Method $Method } elseif ($Property) { Get-NetHelp -Type $Type -Property $Property } else { Get-NetHelp -Type $Type -Detailed:$Detailed } } } elseif ($Type -is [System.__ComObject]) { if ($Online) { if ($Type.PSTypeNames[0] -match 'System\.__ComObject#(.*)$') { if (Test-Path "HKLM:\SOFTWARE\Classes\Interface\$($Matches[1])") { $TypeKey = (Get-ItemProperty "HKLM:\SOFTWARE\Classes\Interface\$($Matches[1])").'(default)' if ('_Application' -contains $TypeKey) { $TypeName = (Get-ItemProperty "HKLM:\SOFTWARE\Classes\TypeLib\$TypeLib\$Version").'(default)' } else { $TypeName = $TypeKey } } } $Uri = "http://social.msdn.microsoft.com/Search/$Culture/?query=$TypeName" [System.Diagnostics.Process]::Start($uri) | Out-Null } else { Write-Error "Local help not supported for COM objects." return } } } } function Resolve-MemberOwnerType { [CmdletBinding()] param( [Parameter(Position = 0)] [System.Management.Automation.PSMethod]$Method ) # TODO: support overloads, support interface definitions $PSCmdlet.WriteVerbose("Resolving owning type of '$($Method.Name)'.") # hackety-hack - this is prone to breaking in the future $TargetType = [System.Management.Automation.PSMethod].GetField("baseObject", "Instance,NonPublic").GetValue($Method) if (($TargetType -isnot [System.Type]) -and (!$TargetType.__CLASS)) { $TargetType = $TargetType.GetType() } if ($TargetType -is [System.Management.ManagementObject]) { $DeclaringType = Get-CimClass $TargetType.__CLASS -Namespace $TargetType.__NAMESPACE } else { if ($Method.OverloadDefinitions -match "static") { $Flags = "Static,Public" } else { $Flags = "Instance,Public" } # TODO: support overloads $MethodInfo = $TargetType.GetMethods($Flags) | Where-Object { $_.Name -eq $Method.Name } | Select-Object -First 1 if (!$MethodInfo) { # this shouldn't happen. throw "Could not resolve owning type." } $DeclaringType = $MethodInfo.DeclaringType } $PSCmdlet.WriteVerbose("Owning type is $($TargetType.FullName). Method declared on $($DeclaringType.FullName).") $DeclaringType } function Get-ObjectVendor { [CmdletBinding()] param( [System.Type]$Type , [switch]$CompanyOnly ) $Assembly = $Type.Assembly $attrib = $Assembly.GetCustomAttributes([Reflection.AssemblyCompanyAttribute], $false) | Select-Object -First 1 if ($attrib.Company) { return $attrib.Company } else { if ($CompanyOnly) { return } # try copyright $attrib = $Assembly.GetCustomAttributes([Reflection.AssemblyCopyrightAttribute], $false) | Select-Object -First 1 if ($attrib.Copyright) { return $attrib.Copyright } } $PSCmdlet.WriteVerbose("Assembly has no [AssemblyCompany] or [AssemblyCopyright] attributes.") } # region FileNameTools Function New-RandomFileName { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "Not changing state")] [cmdletbinding(DefaultParameterSetName = "none")] [Alias("rfn")] [outputtype([string])] Param( [parameter(Position = 0)] [Parameter(ParameterSetName = 'none')] [Parameter(ParameterSetName = 'home')] [Parameter(ParameterSetName = 'temp')] #enter an extension without the leading period e.g 'bak' [string]$Extension, [Parameter(ParameterSetName = 'temp')] [alias("temp")] [Switch]$UseTempFolder, [Parameter(ParameterSetName = 'home')] [alias("home")] [Switch]$UseHomeFolder ) process { if ($UseTempFolder) { $filename = [system.io.path]::GetTempFileName() } elseif ($UseHomeFolder) { $homedocs = [Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments) $filename = Join-Path -Path $homedocs -ChildPath ([system.io.path]::GetRandomFileName()) } else { $filename = [system.io.path]::GetRandomFileName() } if ($Extension) { $original = [system.io.path]::GetExtension($filename).Substring(1) $filename -replace "$original$", $Extension } else { $filename } } } Function New-CustomFileName { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "Not changing state")] [cmdletbinding()] [Alias("cfn")] [outputtype([string])] Param ( [Parameter( Position = 0, Mandatory, HelpMessage = @" You can create a template string using any of these variables, including the % symbol. - %username - %computername - %year - 4 digit year - %yr - 2 digit year - %monthname - The abbreviated month name - %month - The month number - %dayofweek - The full name of the week day - %day - %hour - the hour of the day in 12 hour format to 2 digits - %hour24 - the hour of the day in 24 hour format to 2 digits - %minute - %seconds - %time - A compact string of HourMinuteSecond - %string - A random string - %guid - %### - a random number matching the number of # characters "@)] [ValidateNotNullOrEmpty()] [string]$Template, [ValidateSet("Lower", "Upper", "Default")] [string]$Case = "Default" ) #convert placeholders to lower case but leave everything else as is [regex]$rx = "%\w+(?=%|-|\.|\s|\(|\)|\[|\])" Write-Detail "Starting $($myinvocation.MyCommand)" | Write-Verbose Write-Detail "Processing template: $template" | Write-Verbose $rx.matches($Template) | ForEach-Object { Write-Detail "Converting $($_.value) to lower case" | Write-Verbose $Template = $Template.replace($_.value, $_.value.tolower()) } [string]$filename = $Template Write-Detail "Using filename: $filename" | Write-Verbose $now = Get-Date if ($env:USERNAME) { $user = $env:USERNAME } elseif ($env:USER) { $user = $env:USER } else { $user = "Unknown" } #this needs to be an ordered hashtable so that the regex replacements #will be processed in the right order $hash = [ordered]@{ '%username' = $user '%computername' = [environment]::MachineName '%year' = $now.Year '%yr' = "{0:yy}" -f $now '%monthname' = ("{0:MMM}" -f $now) '%month' = "{0:MM}" -f $now '%dayofweek' = $now.DayOfWeek '%day' = "{0:dd}" -f $now '%hour24' = "{0:HH}" -f $now '%hour' = "{0:hh}" -f $now '%minute' = "{0:mm}" -f $now '%seconds' = "{0:ss}" -f $now '%time' = "{0}{1}{2}" -f $now.hour, $now.minute, $now.Second '%string' = ([system.io.path]::GetRandomFileName()).split(".")[0] '%guid' = [System.Guid]::NewGuid().guid } $hash.GetEnumerator() | ForEach-Object { Write-Detail "Testing $filename for $($_.key)" | Write-Verbose if ($filename -match "($($_.key))") { Write-Detail "replacing $($_.key) with $($_.value)" | Write-Verbose $filename = $filename -replace "($($_.key))", $_.value } } [regex]$rx = '%#+' if ($rx.IsMatch($filename)) { $count = $rx.Match($filename).Value.length - 1 $num = (0..9 | Get-Random -Count 10 | Get-Random -Count $count) -join "" Write-Detail "replacing # with $num" | Write-Verbose $filename = $rx.Replace($filename, $num) } Write-Detail "Converting case to $Case" | Write-Verbose Switch ($Case) { "Upper" { $filename.toUpper() } "Lower" { $filename.ToLower() } default { $filename } } Write-Detail "Ending $($myinvocation.MyCommand)" | Write-Verbose } # endregion FileNameTools function Split-StringOnLiteralString { <# .SYNOPSIS Splits a string based on another literal string (as opposed to regex). .DESCRIPTION The function is designed to split strings the way its expected to be done. It's also designed to be backward-compatible with all versions of PowerShell and has been tested successfully on PowerShell v1. .EXAMPLE $result = Split-StringOnLiteralString 'foo' ' ' # $result.GetType().FullName is System.Object[] # $result.Count is 1 .EXAMPLE $result = Split-StringOnLiteralString 'What do you think of this function?' ' ' # $result.Count is 7 .INPUTS # This function takes two positional arguments # The first argument is a string, and the string to be split # The second argument is a string or char, and it is that which is to split the string in the first parameter .OUTPUTS Output (if any) .NOTES This function always returns an array, even when there is zero or one element in it. # Origin of the Idea by original author; Frank Lesniak. @" The motivation for creating this function was; (1) I wanted a split function that behaved more like VBScript's Split function. (2) I do not want to be messing around with RegEx, and (3) I needed code that was backward-compatible with all versions of PowerShell. "@ - Frank Lesniak. https://github.com/franklesniak/ #> [CmdletBinding()] [OutputType([Object[]])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "The string object to split")] [Alias('String')] [string]$objToSplit, # Spliter char [Parameter(Mandatory = $false, Position = 1)] [Alias('Splitter')] [string]$objSplitter ) process { if ($PsCmdlet.MyInvocation.BoundParameters.ContainsKey('objToSplit')) { if (($objToSplit.Length -gt 2) -or ($objToSplit.Length -eq 0)) { $ex = New-Object -TypeName System.Management.Automation.ItemNotFoundException -ArgumentList "Cannot find path '$aPath' because it does not exist." $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound $errRecord = New-Object System.Management.Automation.ErrorRecord $ex, 'PathNotFound', $category, $aPath $psCmdlet.WriteError($errRecord) Write-Debug "ObjSpliter found" } else { $ex = New-Object -TypeName System.Management.Automation.ItemNotFoundException -ArgumentList "Invalid ObjToSplit" $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound $errRecord = New-Object System.Management.Automation.ErrorRecord $ex, 'InvalidObjToSplit', $category, $aPath $PSCmdlet.ThrowTerminatingError($errRecord) } } if ($null -eq $objToSplit) { $result = @() } if ($null -eq $objSplitter) { Write-Warning -Message 'Object Spliter is empty string' # Splitter was $null; return string to be split within an array (of one element). $result = @($objToSplit) } else { $objSplitterInRegEx = [regex]::Escape($objSplitter) # With the leading comma, force encapsulation into an array so that an array is # returned even when there is one element: $result = @([regex]::Split($objToSplit, $objSplitterInRegEx)) } # The following code forces the function to return an array, always, even when there are zero or one elements in the array [int]$itemCount = 1 if (($null -ne $result) -and $result.GetType().FullName.Contains('[]')) { if (($result.Count -ge 2) -or ($result.Count -eq 0)) { $itemCount = $result.Count } } $strLowercaseFunctionName = $MyInvocation.InvocationName.ToLower() $boolArrayEncapsulation = $MyInvocation.Line.ToLower().Contains('@(' + $strLowercaseFunctionName + ')') -or $MyInvocation.Line.ToLower().Contains('@(' + $strLowercaseFunctionName + ' ') $result = $(if ($boolArrayEncapsulation) { $result } elseif ($itemCount -eq 0) { , @() } elseif ($itemCount -eq 1) { , (, $objToSplit) } else { $result } ) } end { return $result } } function Expand-String { <# .SYNOPSIS Expanding a string expression. Can handle Powershell string expressions or Environment variable expansion. .DESCRIPTION Expanding a string expression. Can handle Powershell string expressions or Environment variable expansion. .PARAMETER String The string that you want expanded. .PARAMETER EnvironmentVariable A switch to expand the string expression as an environment variable. .PARAMETER PowershellString A switch to expand the string expression as a Powershell string .PARAMETER StringResource A switch to expand the string expression as a StringResource which can be found in desktop.ini and registry entries. An example is '@%SystemRoot%\system32\shell32.dll,-21770' .PARAMETER IncludeInput A switch to determine if you want the original string expression to appear in the output. Aliased to 'IncludeOriginal' .EXAMPLE # Expanding Powershell string Expand-String '$psculture' Assuming you have English US as the local installed culture this would return: en-US .EXAMPLE # Expanding Powershell string including original string in the output Expand-String '$psculture' -PsString -IncludeInput #Assuming you have English US as the local installed culture this would return: String Conversion Expanded ------ ---------- -------- $psculture PsString en-US .EXAMPLE # Expanding environment variable Expand-String -String '%PROCESSOR_ARCHITECTURE%' -EnvironmentVariable #Assuming you are a 64 bit machine, the function would return: AMD64 .EXAMPLE # Expanding environment variable including orginal string Expand-String -String '%PROCESSOR_ARCHITECTURE%' -EnvironmentVariable -IncludeInput #Assuming you are a 64 bit machine, the function would return: String Conversion Expanded ------ ---------- -------- %PROCESSOR_ARCHITECTURE% EnvVar AMD64 .EXAMPLE # Resource strings are stored within DLL's and are referenced by an ID number. An example would be # @%SystemRoot%\system32\shell32.dll,-21770 # and they are found in Desktop.ini files and also the registry. $ResourceString = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanWorkstation').DisplayName Expand-String -String $ResourceString -StringResource -IncludeInput # Would return the following if your Windows install culture was en-US String Conversion Expanded ------ ---------- -------- @%systemroot%\system32\wkssvc.dll,-100 StrResource Workstation .NOTES The c# source code was found by me on the Internet, but I can't determine where I originally found it. The ability to expand a StrResource is thanks to that code. #> #region parameter [CmdletBinding(DefaultParameterSetName = 'PsString', ConfirmImpact = 'None')] [OutputType('string')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] param( [Parameter(Mandatory, HelpMessage = 'Enter a string to expand', Position = 0, ValueFromPipeline, ParameterSetName = 'PsString')] [Parameter(Mandatory, HelpMessage = 'Enter a string to expand', Position = 0, ValueFromPipeline, ParameterSetName = 'EnvVar')] [Parameter(Mandatory, HelpMessage = 'Enter a string to expand', Position = 0, ValueFromPipeline, ParameterSetName = 'StrResource')] [string[]] $String, [Parameter(ParameterSetName = 'PsString')] [Alias('PsString')] [switch] $PowershellString, [Parameter(ParameterSetName = 'EnvVar')] [Alias('EnvVar')] [switch] $EnvironmentVariable, [Parameter(ParameterSetName = 'StrResource')] [Alias('StrResource')] [switch] $StringResource, [Parameter(ParameterSetName = 'PsString')] [Parameter(ParameterSetName = 'EnvVar')] [Parameter(ParameterSetName = 'StrResource')] [Alias('IncludeOriginal')] [switch] $IncludeInput ) #endregion parameter begin { Write-Invocation $MyInvocation Out-Verbose "ParameterSetName [$($PsCmdlet.ParameterSetName)]" $source = @' using System; using System.Runtime.InteropServices; using System.Text; public class PFExtractData { [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int LoadString(IntPtr hInstance, int ID, StringBuilder lpBuffer, int nBufferMax); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FreeLibrary(IntPtr hModule); public string ExtractStringFromDLL(string file, int number) { IntPtr lib = LoadLibrary(file); StringBuilder result = new StringBuilder(2048); LoadString(lib, number, result, result.Capacity); FreeLibrary(lib); return result.ToString(); } } '@ Add-Type -TypeDefinition $source $ExtractData = New-Object -TypeName PFExtractData } process { foreach ($currentString in $String) { Out-Verbose "Current string is [$currentString]" $prop = ([ordered] @{ String = $currentString }) switch ($PsCmdlet.ParameterSetName) { 'PsString' { $prop.Conversion = 'PsString' $ReturnVal = $ExecutionContext.InvokeCommand.ExpandString($currentString) } 'EnvVar' { $prop.Conversion = 'EnvVar' $ReturnVal = [System.Environment]::ExpandEnvironmentVariables($currentString) } 'StrResource' { $prop.Conversion = 'StrResource' $Resource = $currentString -split ',' $ReturnVal = $ExtractData.ExtractStringFromDLL([Environment]::ExpandEnvironmentVariables($Resource[0]).substring(1), $Resource[1].substring(1)) # $ReturnVal = 'Placeholder' } } Out-Verbose "ReturnVal is [$ReturnVal]" $prop.Expanded = $ReturnVal if ($IncludeInput) { New-Object -TypeName psobject -Property $prop } else { Write-Output -InputObject $ReturnVal } } } end { Out-Verbose $fxn "Complete." } } function Get-ItemSize { <# .SYNOPSIS Gets file size into a human readable format (B, KB, MB, GB ...) .DESCRIPTION Returns to console host, Formatted sizebytes that are more human readable. .INPUTS None .OUTPUTS Returns a string representation of the file size in a more friendly format based on the passed in bytes. .PARAMETER Size The size of a file in bytes. .PARAMETER IgnoredArguments Allows splatting with arguments that do not apply. Do not use directly. .EXAMPLE gci -File -Recurse -Depth 2 | Sort-Object Length -Descending | Select-Object name, @{l='Size'; e={Get-ItemSize $_.fullName}} .LINK https://github.com/alainQtec/devHelper/blob/main/Private/devHelper.Cli/Public/Get-ItemSize.ps1 #> [CmdletBinding()] [OutputType([PSObject])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [string]$Path, [parameter(ValueFromRemainingArguments = $true)] [Object[]]$ignoredArguments ) Begin { function Format-SizeBytes { param ( [Parameter(Mandatory = $true, Position = 0)] [double]$size ) end { # Do not log function call, it interrupts the single line download progress output. foreach ($unit in @('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB')) { if ($size -lt 1024) { return [string]::Format("{0:0.##} {1}", $size, $unit).Trim(); } $size /= 1024 } return [string]::Format("{0:0.##} YB", $size).Trim(); } } } process { [double]$size = if (Test-Path $Path -PathType Container -ErrorAction SilentlyContinue) { Write-Verbose "Calculating Folder size for $Path ..." Get-ChildItem -Path $Path -File -Recurse -Force | Measure-Object -Property Length -Sum | Select-Object -ExpandProperty sum # Todo: Add a folder size Calculator class with a cool progress Bar } else { Get-Item -Path $Path | Select-Object -ExpandProperty Length } } end { return [PSCustomObject]@{ bytes = $size size = Format-SizeBytes $size Item = Get-Item $Path } } } Function Split-Line { <# .SYNOPSIS To split a string line by line and return a string array .DESCRIPTION To split a string line by line and force it to return a string array. Here strings, script EOL character sequence, and system default NewLine character can muck up parsing a string into lines. This is an attempt to address this. .PARAMETER String The string that you want split line by line .EXAMPLE $HereString = @' Server1 Server2 Server3 '@ $Object = Split-Line -String $HereString $Object.Count 3 .EXAMPLE (Split-Line -String 'SimpleString').GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array #> [CmdletBinding(ConfirmImpact = 'None')] [OutputType('string[]')] param( [Parameter(Mandatory, HelpMessage = 'Enter a string composed of tokens', Position = 0, ValueFromPipeline)] [string] $String ) process { if ($String -notmatch "`n") { Write-Output -InputObject (, ([array] $String)) } else { $ReturnValue = $String -split "`r`n" if ($ReturnValue.Count -eq 1) { $ReturnValue = $String -split "`n" } Write-Output -InputObject $ReturnValue } } } Function Convert-HashtableString { [cmdletbinding()] [OutputType([System.Collections.Hashtable])] Param( [parameter(Mandatory, HelpMessage = "Enter your hashtable string", ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string]$Text ) Begin { Write-Verbose "[BEGIN ] Starting: $($MyInvocation.Mycommand)" } Process { $tokens = $null $err = $null $ast = [System.Management.Automation.Language.Parser]::ParseInput($Text, [ref]$tokens, [ref]$err) $data = $ast.find( { $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $true) if ($err) { Throw $err } else { $data.SafeGetValue() } } End { Write-Verbose "[END ] Ending: $($MyInvocation.Mycommand)" } } Function ConvertTo-Hashtable { [cmdletbinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] [OutputType([System.Collections.Hashtable])] Param( [Parameter( Position = 0, Mandatory, HelpMessage = "Please specify an object", ValueFromPipeline )] [ValidateNotNullorEmpty()] [object]$InputObject, [switch]$NoEmpty, [string[]]$Exclude, [switch]$Alphabetical, [Parameter(HelpMessage = "Create an ordered hashtable instead of a plain hashtable.")] [switch]$Ordered ) Process { <# get type using the [Type] class because deserialized objects won't have a GetType() method which is what I would normally use. #> $TypeName = [system.type]::GetTypeArray($InputObject).name Write-Verbose "Converting an object of type $TypeName" #get property names using Get-Member $names = $InputObject | Get-Member -MemberType properties | Select-Object -ExpandProperty name if ($Alphabetical) { Write-Verbose "Sort property names alphabetically" $names = $names | Sort-Object } #define an empty hash table if ($Ordered) { Write-Verbose "Creating an ordered hashtable" $hash = [ordered]@{ } } else { $hash = @{ } } #go through the list of names and add each property and value to the hash table $names | ForEach-Object { #only add properties that haven't been excluded if ($Exclude -notcontains $_) { #only add if -NoEmpty is not called and property has a value if ($NoEmpty -AND !($inputobject.$_)) { Write-Verbose "Skipping $_ as empty" } else { Write-Verbose "Adding property $_" $hash.Add($_, $inputobject.$_) } } else { Write-Verbose "Excluding $_" } } Write-Verbose "Writing the result to the pipeline" Write-Output $hash } } Function Join-Hashtable { [cmdletbinding()] [OutputType([System.Collections.Hashtable])] Param ( [hashtable]$First, [hashtable]$Second, [switch]$Force ) #create clones of hashtables so originals are not modified $Primary = $First.Clone() $Secondary = $Second.Clone() #check for any duplicate keys $duplicates = $Primary.keys | Where-Object { $Secondary.ContainsKey($_) } if ($duplicates) { foreach ($item in $duplicates) { if ($force) { #force primary key, so remove secondary conflict $Secondary.Remove($item) } else { Write-Host "Duplicate key $item" -ForegroundColor Yellow Write-Host "A $($Primary.Item($item))" -ForegroundColor Yellow Write-Host "B $($Secondary.Item($item))" -ForegroundColor Yellow $r = Read-Host "Which key do you want to KEEP [AB]?" if ($r -eq "A") { $Secondary.Remove($item) } elseif ($r -eq "B") { $Primary.Remove($item) } Else { Write-Warning "Aborting operation" Return } } #else prompt } } #join the two hash tables $Primary + $Secondary } Function Rename-Hashtable { [cmdletbinding(SupportsShouldProcess, DefaultParameterSetName = "Pipeline")] [alias("rht")] Param( [parameter( Position = 0, Mandatory, HelpMessage = "Enter the name of your hash table variable without the `$", ParameterSetName = "Name" )] [ValidateNotNullorEmpty()] [string]$Name, [parameter( Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = "Pipeline" )] [ValidateNotNullorEmpty()] [object]$InputObject, [parameter( Position = 1, Mandatory, HelpMessage = "Enter the existing key name you want to rename")] [ValidateNotNullorEmpty()] [string]$Key, [parameter(position = 2, Mandatory, HelpMessage = "Enter the NEW key name" )] [ValidateNotNullorEmpty()] [string]$NewKey, [switch]$Passthru, [ValidateSet("Global", "Local", "Script", "Private", 0, 1, 2, 3)] [ValidateNotNullOrEmpty()] [string]$Scope = "Global" ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" Write-Verbose "using parameter set $($PSCmdlet.ParameterSetName)" } Process { Write-Verbose "PSBoundparameters" Write-Verbose $($PSBoundParameters | Out-String) #validate Key and NewKey are not the same if ($key -eq $NewKey) { Write-Warning "The values you specified for -Key and -NewKey appear to be the same. Names are NOT case-sensitive" #bail out Return } Try { #validate variable is a hash table if ($InputObject) { #create a completely random name to avoid any possible naming collisions $name = [system.io.path]::GetRandomFileName() Write-Verbose "Creating temporary hashtable ($name) from pipeline input" Set-Variable -Name $name -Scope $scope -Value $InputObject -WhatIf:$False $passthru = $True } else { Write-Verbose "Using hashtable variable $name" } Write-Verbose (Get-Variable -Name $name -Scope $scope | Out-String) Write-Verbose "Validating $name as a hashtable in $Scope scope." #get the variable $var = Get-Variable -Name $name -Scope $Scope -ErrorAction Stop Write-Verbose "Detected a $($var.value.GetType().fullname)" Write-Verbose "Testing for key $key" if (!$var.value.Contains($key)) { Write-Warning "Failed to find the key $key in the hashtable." #bail out Return } if ( $var.Value -is [hashtable]) { #create a temporary copy Write-Verbose "Cloning a temporary hashtable" <# Use the clone method to create a separate copy. If you just assign the value to $temphash, the two hash tables are linked in memory so changes to $tempHash are also applied to the original object. #> $tempHash = $var.Value.Clone() if ($pscmdlet.ShouldProcess($NewKey, "Replace key $key")) { Write-Verbose "Writing the new hashtable to variable named $hashname" #create a key with the new name using the value from the old key Write-Verbose "Adding new key $newKey to the temporary hashtable" $tempHash.Add($NewKey, $tempHash.$Key) #remove the old key Write-Verbose "Removing $key" $tempHash.Remove($Key) #write the new value to the variable Write-Verbose "Writing the new hashtable to variable named $Name" Write-Verbose ($tempHash | Out-String) Set-Variable -Name $Name -Value $tempHash -Scope $Scope -Force -PassThru:$Passthru | Select-Object -ExpandProperty Value } } elseif ($var.value -is [System.Collections.Specialized.OrderedDictionary]) { Write-Verbose "Processing as an ordered dictionary" $varHash = $var.value #find the index number of the existing key $i = -1 Do { $i++ } Until (($varHash.GetEnumerator().name)[$i] -eq $Key) #save the current value $val = $varhash.item($i) if ($pscmdlet.ShouldProcess($NewKey, "Replace key $key at $i")) { #remove at the index number $varhash.RemoveAt($i) #insert the new value at the index number $varhash.Insert($i, $NewKey, $val) Write-Verbose "Writing the new hashtable to variable named $name" Write-Verbose ($varHash | Out-String) Set-Variable -Name $name -Value $varhash -Scope $Scope -Force -PassThru:$Passthru | Select-Object -ExpandProperty Value } } else { Write-Warning "The variable $name does not appear to be a hash table or ordered dictionaryBet" } } Catch { Write-Warning "Failed to find a variable with a name of $Name. $($_.exception.message)." } Write-Verbose "Rename complete." } End { #clean up any temporary variables if ($InputObject) { Write-Verbose "Removing temporary variable $name" Remove-Variable -Name $Name -Scope $scope -WhatIf:$False } Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } } function Get-ManagedDll { [CmdletBinding()][OutputType([string])] Param ( [Parameter(Mandatory = $True)] [String]$FilePath ) begin { $output = @() } process { $Path = Resolve-Path $FilePath if (! [IO.File]::Exists($Path)) { Throw "$Path does not exist." } $FileBytes = [System.IO.File]::ReadAllBytes($Path) if (($FileBytes[0..1] | ForEach-Object { [Char]$_ }) -join '' -cne 'MZ') { Throw "$Path is not a valid executable." } $Length = $FileBytes.Length $CompressedStream = New-Object IO.MemoryStream $DeflateStream = New-Object IO.Compression.DeflateStream ($CompressedStream, [IO.Compression.CompressionMode]::Compress) $DeflateStream.Write($FileBytes, 0, $FileBytes.Length) $DeflateStream.Dispose() $CompressedFileBytes = $CompressedStream.ToArray() $CompressedStream.Dispose() $EncodedCompressedFile = [Convert]::ToBase64String($CompressedFileBytes) Write-Verbose "Compression ratio: $(($EncodedCompressedFile.Length/$FileBytes.Length).ToString('#%'))" $Output = @" `$EncodedCompressedFile = @' $EncodedCompressedFile '@ `$DeflatedStream = New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String(`$EncodedCompressedFile),[IO.Compression.CompressionMode]::Decompress) `$UncompressedFileBytes = New-Object Byte[]($Length) `$DeflatedStream.Read(`$UncompressedFileBytes, 0, $Length) | Out-Null [Reflection.Assembly]::Load(`$UncompressedFileBytes) "@ } end { return $Output } } function New-DigitalSignature { <# .SYNOPSIS Creates a digital signature. .DESCRIPTION Generates a digital signature for a file using a private key. .PARAMETER FilePath The path to the file to be signed. .PARAMETER PrivateKeyPath The path to the private key file to use for signing. .PARAMETER SignatureOutputPath The path where the generated signature will be saved. .NOTES The signature will be generated using the SHA256 hash algorithm and the RSA public-key cryptosystem. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [string]$FilePath, [Parameter(Mandatory = $true)] [string]$PrivateKeyPath, [Parameter(Mandatory = $true)] [string]$SignatureOutputPath ) begin {} process { if ($PSCmdlet.ShouldProcess("Target", "Operation")) { Write-Verbose "" } } end {} } function Compress-Data { [CmdletBinding(DefaultParameterSetName = 'bytes')] [OutputType([byte[]], [string])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'bytes')] [alias('b')][byte[]]$Bytes, [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'str')] [Alias("t", "str")][String]$Plaintext, [Parameter(Mandatory = $false, Position = 1, ParameterSetName = '__AllParameters')] [Alias('c')][Compression]$Compression = 'Gzip' ) process { $res = $null if ($PSCmdlet.ParameterSetName -eq 'bytes') { $res = Compress-Data -b $Bytes -c $Compression } else { $res = Compress-Data -t $Plaintext -c $Compression } } end { return $res } } function Expand-Data { [CmdletBinding(DefaultParameterSetName = 'bytes')] [OutputType([byte[]], [string])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'bytes')] [alias('b')][byte[]]$Bytes, [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'str')] [Alias("t", "str")][String]$Plaintext, [Parameter(Mandatory = $false, Position = 1, ParameterSetName = '__AllParameters')] [Alias('c')][Compression]$Compression = 'Gzip' ) process { $res = $null if ($PSCmdlet.ParameterSetName -eq 'bytes') { $res = Expand-Data -b $bytes -c $Compression } else { $res = Expand-Data -t $Plaintext -c $Compression } } end { return $res } } function New-K3Y { <# .SYNOPSIS Creates a new [K3Y] object .DESCRIPTION Creates a custom k3y object for encryption/decryption. The K3Y can only be used to Once, and its 'UID' [ see .SetK3YUID() method ] is a fancy way of storing the version, user/owner credentials, Compression alg~tm used and Other Info about the most recent use and the person who used it; so it can be analyzed later to verify some rules before being used again. this allows to create complex expiring encryptions. .EXAMPLE $K = New-K3Y (Get-Credential -UserName 'Alain Herve' -Message 'New-K3Y') .NOTES This is a private function, its not meant to be exported, or used alone #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = '')] [CmdletBinding(DefaultParameterSetName = 'default')] [OutputType([K3Y], [string])] param ( # Parameter help description [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'byPscredential')] [Alias('Owner')][ValidateNotNull()] [pscredential]$User, # Parameter help description [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'default')] [string]$UserName, # Parameter help description [Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'default')] [securestring]$Password, # Expiration date [Parameter(Position = 2, Mandatory = $false, ParameterSetName = 'default')] [Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'byPscredential')] [datetime]$Expiration, # Convert to string (sharable) [Parameter(Mandatory = $false, ParameterSetName = '__AllParameterSets')] [switch]$AsString, [Parameter(Mandatory = $false, ParameterSetName = '__AllParameterSets')] [switch]$Protect ) begin { $k3y = $null $params = $PSCmdlet.MyInvocation.BoundParameters $IsInteractive = [Environment]::UserInteractive -and [Environment]::GetCommandLineArgs().Where({ $_ -like '-NonI*' }).Count -eq 0 } process { $k3y = $(if ($PSCmdlet.ParameterSetName -eq 'byPscredential') { if ($params.ContainsKey('User') -and $params.ContainsKey('Expiration')) { [K3Y]::New($User, $Expiration); } else { # It means: $params.ContainsKey('User') -and !$params.ContainsKey('Expiration') [datetime]$ExpiresOn = if ($IsInteractive) { [int]$days = Read-Host -Prompt "Expires In (replie num of days)" [datetime]::Now + [Timespan]::new($days, 0, 0, 0); } else { [datetime]::Now + [Timespan]::new(30, 0, 0, 0); # ie: expires in 30days } [K3Y]::New($User, $ExpiresOn); } } elseif ($PSCmdlet.ParameterSetName -eq 'default') { if ($params.ContainsKey('UserName') -and $params.ContainsKey('Password') -and $params.ContainsKey('Expiration')) { [K3Y]::New($UserName, $Password, $Expiration); } elseif ($params.ContainsKey('UserName') -and $params.ContainsKey('Password') -and !$params.ContainsKey('Expiration')) { [K3Y]::New($UserName, $Password); } elseif ($params.ContainsKey('UserName') -and !$params.ContainsKey('Password') -and !$params.ContainsKey('Expiration')) { $passwd = if ($IsInteractive) { Read-Host -AsSecureString -Prompt "Password" } else { [securestring]::new() } [K3Y]::New($UserName, $passwd); } elseif (!$params.ContainsKey('UserName') -and $params.ContainsKey('Password') -and !$params.ContainsKey('Expiration')) { $usrName = if ($IsInteractive) { Read-Host -Prompt "UserName" } else { [System.Environment]::GetEnvironmentVariable('UserName') } [K3Y]::New($usrName, $Password); } elseif (!$params.ContainsKey('UserName') -and !$params.ContainsKey('Password') -and $params.ContainsKey('Expiration')) { if ($IsInteractive) { $usrName = Read-Host -Prompt "UserName"; $passwd = Read-Host -AsSecureString -Prompt "Password"; [K3Y]::New($usrName, $passwd); } else { [K3Y]::New($Expiration); } } elseif (!$params.ContainsKey('UserName') -and $params.ContainsKey('Password') -and $params.ContainsKey('Expiration')) { $usrName = if ($IsInteractive) { Read-Host -Prompt "UserName" } else { [System.Environment]::GetEnvironmentVariable('UserName') } [K3Y]::New($usrName, $Password, $Expiration); } else { [K3Y]::New(); } } else { Write-Verbose "System.Management.Automation.ParameterBindingException: Could Not Resolve ParameterSetname." [K3Y]::New(); } ) if ($Protect.IsPresent) { $k3y.User.Protect() }; } end { if ($AsString.IsPresent) { return ($k3y | xconvert ToString) } return $k3y } } #region Encrpt-Decrp function Get-EncryptionAlgorithm { <# .SYNOPSIS Used to set the encryption algorithm that will be used by other functions in the module to encrypt and decrypt data. .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not supported in Linux' .LINK https://github.com/alainQtec/cliHelper.Core/blob/main/Public/Encrpt_Decrpt/Get-EncryptionAlgorithm.ps1 .EXAMPLE Get-EncryptionAlgorithm -key "dsfjkmsjkfnsdkcnmdimsidfcsdcmsdlkxiddsdcmsdlcdlilsdldd " Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [CmdletBinding(DefaultParameterSetName = 'default')] [OutputType([string])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'default')] [string]$key, [Parameter(Mandatory = $true, ParameterSetName = 'k')] [K3Y]$k3y ) begin { $algorthm = [String]::Empty } process { # Parse the Object to return the Name of encryption Algorithm } end { return $algorthm } } function Get-EncryptedObject { <# .SYNOPSIS Applies several paranoid encryptions to an Object or a file. .DESCRIPTION Encryption can be applied to any item that can be converted to a byte array. This function may currently encrypt Objects (i.e. "System.Object") and files. The function employs Rijndael AES-256, Rivest-Shamir-Adleman encryption (RSA), MD5 Triple D.E.S, and other algorithms. Yeah, It gets Pretty paranoid! There is an option to store your encryption key(s) in Windows Password vault so that the Decryptor Function (Decryp) can use them without need of your input again. .NOTES # Some Points to Consider When Using This function: 1. If you don't feel safe when typing or sending sensitive info to the terminal/console or via RMM, Then its better to use some nerdy function that uses the best well known/tested/approved standard algorithms That way, you know your data is secure enough. This was the whole reason why I created this function. 2. One of this script's flaws is that it is a script (a non-obfuscated, cleartext script!). If you or some hacker can't get the password but have the source code you can reverse engineer to findout why you are not getting clear output. Thus allowing to bruteforce untill you get cleartext. Although I doubt that AES-256-GCM can be brute forced if you used a strong Password. Even though that eventuality is unlikely, ensure that the source code (Modified Version of this Script or anything...) is never leaked in production. Perhaps compile it to an encrypted binary or something. 3. Sometimes even your local password vault is not secure enough! i.e: Read: https://www.hackingarticles.in/credential-dumping-windows-credential-manager/ So If you feel unsafe Retrieve your stuff from WindowsCredentialManager, Store them on a Goober or somethin Then clean your local vault, ie: if (![bool]("Windows.Security.Credentials.PasswordVault" -as 'type';)) { [Windows.Security.Credentials.PasswordVault, Windows.Security.Credentials, ContentType = WindowsRuntime] } $vault = [Windows.Security.Credentials.PasswordVault]::new() # Suppose you have stuff in your vault. ex: # $vault.Add([Windows.Security.Credentials.PasswordCredential]::new(';MySecretPlan';, $(whoami), "#Test`nThis is my secret Plan written in MarkDown...")) $VaultContent = $vault.RetrieveAll() | select resource, userName | % {$vault.Retrieve($_.Resource, $_.UserName)} | select UserName, Resource, @{l=';Content';; e={$_.Password}}; $VaultContent | ConvertTo-Json | Set-Content -Path $PathtoMyGoober\MyLocalVault_Export.json -Encoding UTF8 $(Get-Item $PathtoMyGoober\MyLocalVault_Export.json).Encrypt(); $vault.RetrieveAll() | % { $vault.Remove($vault.Retrieve($_.Resource, $_.UserName)); Write-verbose "[i] Removed $($_.Resource)" } .LINK https://github.com/alainQtec/cliHelper.Core/blob/main/Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1 .EXAMPLE $enc = Encrypt -Object "Hello World!" -Password $([ArgonCage]::GetPassword()) -KeyOutFile .\PublicKee.txt $dec = Decrypt -InputBytes $enc -Password $([ArgonCage]::GetPassword()) -PublicKey $(cat .\PublicKee.txt) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '', Justification = 'Prefer verb usage')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertSecurestringWithPlainText", '')] [CmdletBinding(ConfirmImpact = "Medium", DefaultParameterSetName = 'WithSecureKey')] [Alias('Encrypt', 'Encrypt-Object')] [OutputType([byte[]])] param ( # The Object you want to encrypt [Parameter(Mandatory = $true, Position = 0, ParameterSetName = '__AllParameterSets')] [Alias('InputObj')] $Object, # Use a strong password. It will be used Lock Your local Key (ConvertTo-SecureString -String "Message" -SecureKey [System.Security.SecureString]) before storing in vault. # Add this if you want 3rd layer of security. Useful when someone(Ex: Hacker) has somehow gained admin priviledges of your PC; # With a locked local Password vault it will require much more than just guessing The password, or any BruteForce tool. [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithSecureKey')] [Alias('Password', 'Securestring')] [SecureString]$PrivateKey = [ArgonCage]::GetPassword(), [Parameter(Mandatory = $false, Position = 2, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [string]$PublicKey, # Source or the Encryption Key. Full/Path of the keyfile you already have. It will be used to lock your keys. (ConvertTo-SecureString -String "Message" -Key [Byte[]]) [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithKey')] [ValidateNotNullOrEmpty()] [Byte[]]$Key, # Path OF the KeyFile (Containing You saved key base64String Key) [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithKeyFile')] [ValidateNotNullOrEmpty()] [string]$KeyFile, # FilePath to store your keys. Saves keys as base64 in an enrypted file. Ex: some_random_Name.key (Not recomended) [Parameter(Mandatory = $false, Position = 3, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [Alias('ExportFile')] [string]$KeyOutFile, # How long you want the encryption to last. Default to one month (!Caution Your data will be LOST Forever if you do not decrypt before the Expiration date!) [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithVault')] [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'WithKey')] [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'WithPlainKey')] [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'WithSecureKey')] [ValidateNotNullOrEmpty()] [Alias('KeyExpiration')] [datetime]$Expiration = ([Datetime]::Now + [TimeSpan]::new(30, 0, 0, 0)), [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'WithSecureKey')] [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'WithPlainKey')] [Parameter(Mandatory = $false, Position = 4, ParameterSetName = 'WithVault')] [Parameter(Mandatory = $false, Position = 5, ParameterSetName = 'WithKey')] [ValidateNotNullOrEmpty()] [int]$Iterations = 2, [Parameter(Mandatory = $false, Position = 6, ParameterSetName = '__AllParameterSets')] [ValidateScript({ if ([Enum]::GetNames([CryptoAlgorithm]).Contains($_)) { return $true } throw 'Invalid CryptoAlgorithm' } )][Alias('CryptoAlgorithm')] [ValidateNotNullOrEmpty()] [string]$Algorithm ) DynamicParam { $DynamicParams = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() [bool]$IsPossiblefileType = $false [bool]$IsArrayObject = $false [int]$P = 7 #(Position) try { if ($Object.count -gt 1) { $InputType = @() $IsArrayObject = $true foreach ($Obj in $Object) { $InputType += $Obj.GetType() } $InputType = $InputType | Sort-Object -Unique } else { $InputType = $Object.GetType() } } catch { $InputType = [string]::Empty } $IsPossiblefileTypes = @('string', 'string[]', 'System.IO.FileInfo', 'System.IO.FileInfo[]', 'System.Object', 'System.Object[]') if ($IsArrayObject) { foreach ($type in $InputType) { $IsPossiblefileType = [bool]($type -in $IsPossiblefileTypes) -or $IsPossiblefileType } } else { $IsPossiblefileType = [bool]($InputType -in $IsPossiblefileTypes) } #region OutFile if ($IsPossiblefileType) { $attributes = [System.Management.Automation.ParameterAttribute]::new(); $attHash = @{ Position = $P ParameterSetName = '__AllParameterSets' Mandatory = $False ValueFromPipeline = $false ValueFromPipelineByPropertyName = $false ValueFromRemainingArguments = $false HelpMessage = 'Use to specify Output File, if inputObject is a file.' DontShow = $False }; $attHash.Keys | ForEach-Object { $attributes.$_ = $attHash.$_ } $attributeCollection.Add($attributes); $attributeCollection.Add([System.Management.Automation.ValidateNotNullOrEmptyAttribute]::new()) $attributeCollection.Add([System.Management.Automation.AliasAttribute]::new([System.String[]]('OutPutFile', 'DestinationFile'))) $RuntimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new("OutFile", [Object], $attributeCollection) $DynamicParams.Add("OutFile", $RuntimeParam) $P++ } #endregion OutFile #region IgnoredArguments $attributes = [System.Management.Automation.ParameterAttribute]::new(); $attHash = @{ Position = $P ParameterSetName = '__AllParameterSets' Mandatory = $False ValueFromPipeline = $true ValueFromPipelineByPropertyName = $true ValueFromRemainingArguments = $true HelpMessage = 'Allows splatting with arguments that do not apply. Do not use directly.' DontShow = $False }; $attHash.Keys | ForEach-Object { $attributes.$_ = $attHash.$_ } $attributeCollection.Add($attributes) $RuntimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new("IgnoredArguments", [Object[]], $attributeCollection) $DynamicParams.Add("IgnoredArguments", $RuntimeParam) #endregion IgnoredArguments return $DynamicParams } begin { $eap = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $PsCmdlet.MyInvocation.BoundParameters.GetEnumerator() | ForEach-Object { New-Variable -Name $_.Key -Value $_.Value -ea 'SilentlyContinue' } $PsW = [securestring]::new(); $nc = $null; $fxn = ('[' + $MyInvocation.MyCommand.Name + ']') $ExportsPNK = $PsCmdlet.MyInvocation.BoundParameters.ContainsKey('KeyOutFile') -and ![string]::IsNullOrEmpty($KeyOutFile) if ($PsCmdlet.ParameterSetName -ne 'WithKey' -and !$ExportsPNK) { throw 'Plese specify PublicKey "ExportFile/Outfile" Parameter.' } # Write-Invocation $MyInvocation } process { Write-Verbose "[+] $fxn $($PsCmdlet.ParameterSetName) ..." Set-Variable -Name PsW -Scope Local -Visibility Private -Option Private -Value $(switch ($PsCmdlet.ParameterSetName) { 'WithKey' { } 'WithVault' { } 'WithSecureKey' { $PrivateKey } Default { throw 'Error!' } } ); Set-Variable -Name nc -Scope Local -Visibility Private -Option Private -Value $([xcrypt]::new($Object)); if ($PsCmdlet.MyInvocation.BoundParameters.ContainsKey('Expiration')) { $nc.key.Expiration = [Expiration]::new($Expiration) } if ($PsCmdlet.MyInvocation.BoundParameters.ContainsKey('PublicKey')) { $nc.SetPNKey($PublicKey); } else { Write-Verbose "[+] Create PublicKey (K3Y) ..."; $PNK = New-K3Y -UserName $nc.key.User.UserName -Password $PsW -Expiration $nc.key.Expiration.date -AsString -Protect $nc.SetPNKey($PNK); } $encryptor = [Encryptor]::new($bytesToEncrypt, [securestring]$Password, [byte[]]$salt, [CryptoAlgorithm]$Algorithm); $encrypted = $encryptor.encrypt($Iterations); $bytes = $encrypted if ($ExportsPNK) { Write-Verbose "[i] Export PublicKey (PNK) to $KeyOutFile ..." $nc.key.Export($KeyOutFile, $true); } $bytes = $(if ($bytes.Equals($nc.Object.Bytes)) { $null }else { $nc.Object.Bytes }) } end { $ErrorActionPreference = $eap return $bytes } } function Get-DecryptedObject { <# .SYNOPSIS Decryts Objects or files. .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Caveats about the function: 'This function is not fully supported in Linux' .LINK Specify a URI to a help page, this will show when Get-Help -Online is used. .EXAMPLE $msg = "My email: alain.1337dev@outlook.com" $enc = Encrypt $msg -Password $([ArgonCage]::GetPassword()) -KeyOutFile .\PublicKee.txt $dec = Decrypt $enc -Password $([ArgonCage]::GetPassword()) -PublicKey $(cat .\PublicKee.txt) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '', Justification = 'Prefer verb usage')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertSecurestringWithPlainText", '')] [CmdletBinding(ConfirmImpact = "Medium", DefaultParameterSetName = 'WithSecureKey')] [Alias('Decrypt', 'Decrypt-Object')] [OutputType([byte[]])] param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [Alias('Bytes')] [byte[]]$InputBytes, [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithSecureKey')] [ValidateNotNullOrEmpty()] [Alias('Password')] [SecureString]$PrivateKey = [ArgonCage]::GetPassword(), [Parameter(Mandatory = $true, Position = 2, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [string]$PublicKey, # Source or the Encryption Key. Full/Path of the keyfile you already have. It will be used to lock your keys. (ConvertTo-SecureString -String "Message" -Key [Byte[]]) [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithKey')] [ValidateNotNullOrEmpty()] [Byte[]]$Key, # Path OF the KeyFile (Containing You saved key base64String Key) [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithKeyFile')] [ValidateNotNullOrEmpty()] [string]$KeyFile, [Parameter(Mandatory = $false, Position = 4, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [int]$Iterations = 2, [Parameter(Mandatory = $false, Position = 5, ParameterSetName = '__AllParameterSets')] [ValidateScript({ if ([Enum]::GetNames([CryptoAlgorithm]).Contains($_)) { return $true } throw 'Invalid CryptoAlgorithm' } )][Alias('CryptoAlgorithm')] [ValidateNotNullOrEmpty()] [string]$Algorithm ) begin { $eap = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $fxn = ('[' + $MyInvocation.MyCommand.Name + ']'); # Write-Invocation $MyInvocation } process { Write-Verbose "[+] $fxn $($PsCmdlet.ParameterSetName) ..." $PsW = switch ($PsCmdlet.ParameterSetName) { 'WithKey' { } 'WithVault' { } 'WithSecureKey' { $PrivateKey } Default { [xcrypt]::new() } } $salt = [byte[]]::new() $decryptor = [Decryptor]::new($InputBytes, [securestring]$PsW, [byte[]]$salt, [CryptoAlgorithm]$Algorithm); $decrypted = $Decryptor.encrypt($Iterations); $bytes = $decrypted if ($PsCmdlet.ParameterSetName -ne 'WithKey' -and $PsCmdlet.MyInvocation.BoundParameters.ContainsKey('KeyOutFile')) { if (![string]::IsNullOrEmpty($KeyOutFile)) { Write-Verbose "[i] Export PublicKey (PNK) to $KeyOutFile ..." $nc.key.Export($KeyOutFile, $true) } } $bytes = $(if ($bytes.Equals($nc.Object.Bytes)) { $null }else { $nc.Object.Bytes }) } end { $ErrorActionPreference = $eap return $bytes } } function Protect-Data { <# .SYNOPSIS Protects Data so that it won't be decipherd unless by on that same PC .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not fully supported in Linux' .LINK https://github.com/alainQtec/cliHelper.Core/blob/main/Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1 .EXAMPLE [securestring]$sec = Protect-Data $(Read-Host -AsSecurestring -Prompt 'Secret msg') #> [CmdletBinding(ConfirmImpact = "Medium", DefaultParameterSetName = 'String', SupportsShouldProcess = $true)] [Alias('Protect')] [OutputType([Object[]])] param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'String')] [ValidateNotNullOrEmpty()] [string]$MSG, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'SecureString')] [ValidateNotNullOrEmpty()] [securestring]$SecureMSG, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Bytes')] [ValidateNotNullOrEmpty()] [byte[]]$Bytes, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Xml')] [ValidateNotNullOrEmpty()] [Alias('XmlDoc')] [xml]$InputXml, [Parameter(Mandatory = $false, Position = 1, ParameterSetName = '__AllParameterSets')] [ValidateSet('User', 'Machine')] [ValidateNotNullOrEmpty()] [Alias('ProtectionScope')] [string]$Scope = 'User', [Parameter(Mandatory = $false, Position = 2, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [byte[]]$Entropy ) begin { #Load The Assemblies if (!("System.Security.Cryptography.ProtectedData" -is 'Type')) { Add-Type -AssemblyName System.Security } [bool]$UseCustomEntropy = $null -ne $Entropy -and $PsCmdlet.MyInvocation.BoundParameters.ContainsKey('Entropy') } process { $ProtectedD = switch ($PsCmdlet.ParameterSetName) { 'Xml' { if ($PSCmdlet.ShouldProcess("Xml", "Protect")) { if ($UseCustomEntropy) { Protect-Data -Bytes $($InputXml | xconvert ToBytes) -Scope $Scope -Entropy $Entropy } else { Protect-Data -Bytes $($InputXml | xconvert ToBytes) -Scope $Scope } } } 'string' { if ($PSCmdlet.ShouldProcess("String", "Protect")) { if ($UseCustomEntropy) { Protect-Data -MSG $Msg -Scope $Scope -Entropy $Entropy } else { Protect-Data -MSG $Msg -Scope $Scope } } } 'Bytes' { if ($PSCmdlet.ShouldProcess("Bytes", "Protect")) { if ($UseCustomEntropy) { Protect-Data -Bytes $Bytes -Scope $Scope -Entropy $Entropy } else { Protect-Data -Bytes $Bytes -Scope $Scope } } } 'SecureString' { throw 'Yeet!' } Default { throw 'Error!' } } } end { return $ProtectedD } } function UnProtect-Data { <# .SYNOPSIS Unprotects data .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not fully supported in Linux' .LINK https://github.com/alainQtec/cliHelper.Core/blob/main/Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1 .EXAMPLE UnProtect-Data $secretMsg #> [CmdletBinding(ConfirmImpact = "Medium", DefaultParameterSetName = 'string', SupportsShouldProcess = $true)] [Alias('UnProtect')] [OutputType([byte[]])] param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'String')] [ValidateNotNullOrEmpty()] [string]$MSG, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'SecureString')] [ValidateNotNullOrEmpty()] [securestring]$SecureMSG, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Bytes')] [ValidateNotNullOrEmpty()] [byte[]]$Bytes, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Xml')] [ValidateNotNullOrEmpty()] [Alias('XmlDoc')] [xml]$InputXml, [Parameter(Mandatory = $false, Position = 1, ParameterSetName = '__A llParameterSets')] [ValidateSet('User', 'Machine')] [ValidateNotNullOrEmpty()] [Alias('ProtectionScope')] [string]$Scope = 'User', [Parameter(Mandatory = $false, Position = 2, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [byte[]]$Entropy ) begin { #Load The Assemblies if (!("System.Security.Cryptography.ProtectedData" -is 'Type')) { Add-Type -AssemblyName System.Security } [bool]$UseCustomEntropy = $null -ne $Entropy -and $PsCmdlet.MyInvocation.BoundParameters.ContainsKey('Entropy') } process { $UnProtected = switch ($PsCmdlet.ParameterSetName) { 'Xml' { if ($PSCmdlet.ShouldProcess("Xml", "Protect")) { if ($UseCustomEntropy) { UnProtect-Data -Bytes $($InputXml | xconvert ToBytes) -Entropy $Entropy -Scope $Scope } else { UnProtect-Data -Bytes $($InputXml | xconvert ToBytes) -Scope $Scope } } } 'string' { if ($PSCmdlet.ShouldProcess("String", "Protect")) { if ($UseCustomEntropy) { UnProtect-Data -MSG $Msg -Scope $Scope -Entropy $Entropy } else { UnProtect-Data -MSG $Msg -Scope $Scope } } } 'Bytes' { if ($PSCmdlet.ShouldProcess("Bytes", "Protect")) { if ($UseCustomEntropy) { UnProtect-Data -Bytes $Bytes -Scope $Scope -Entropy $Entropy } else { UnProtect-Data -Bytes $Bytes -Scope $Scope } } } 'SecureString' { throw 'Yeet!' } Default { throw 'Error!' } } } end { return $UnProtected } } #endregion Encrpt-Decrp #region Local_Vault function Get-SavedCredential { <# .SYNOPSIS Get SavedCredential .DESCRIPTION Gets Saved Credential from credential vault .NOTES This function is not supported on Linux .LINK https://github.com/alainQtec/cliHelper.Core .EXAMPLE Get-SavedCredential 'My App' Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [CmdletBinding(DefaultParameterSetName = 'default')] [OutputType([CredManaged])] param ( # Target /title /name of the saved credential [Parameter(Position = 0, Mandatory = $false, ParameterSetName = '__AllParameterSets')] [Alias('Name', 'TargetName')][ValidateNotNullOrEmpty()] [string]$Target, # Username / Owner [Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'default')] [Parameter(Position = 2, Mandatory = $false, ParameterSetName = 'byCrtyp')] [Alias('usrnm')][ValidateNotNullOrEmpty()] [string]$UserName, # Credential type. [Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'byCrtyp')] [ValidateSet('Generic', 'DomainPassword', 'DomainCertificate', 'DomainVisiblePassword', 'GenericCertificate', 'DomainExtended', 'Maximum', 'MaximumEx')] [Alias('CredType')][ValidateNotNullOrEmpty()] [string]$Type = 'Generic' ) begin { $CredentialManager = [CredentialManager]::new(); $Savd_Cred = $null $params = $PSCmdlet.MyInvocation.BoundParameters; $GetTargetName = [scriptblock]::Create({ if ([Environment]::UserInteractive -and [Environment]::GetCommandLineArgs().Where({ $_ -like '-NonI*' }).Count -eq 0) { $t = Read-Host -Prompt "TargetName" if ([string]::IsNullOrWhiteSpace($t)) { throw 'Null Or WhiteSpace targetName is not valid' } $t } else { throw 'Please Input valid Name' } } ) } process { $_Target = $(if ($params.ContainsKey('Target') -and [string]::IsNullOrWhiteSpace($Target)) { Invoke-Command -ScriptBlock $GetTargetName } elseif (!$params.ContainsKey('Target')) { Invoke-Command -ScriptBlock $GetTargetName } else { $Target } ) $Savd_Cred = $(if ($PSCmdlet.ParameterSetName -eq 'default') { $CredentialManager.GetCredential($_Target, $UserName) } elseif ($PSCmdlet.ParameterSetName -eq 'byCrtyp') { if ($params.ContainsKey('type')) { $CredentialManager.GetCredential($_Target, $Type, $UserName) } else { $CredentialManager.GetCredential($_Target, $Type, $UserName) } } ) if ([CredentialManager]::LastErrorCode.Equals([CredentialManager]::ERROR_NOT_FOUND)) { throw [CredentialNotFoundException]::new("$_Target not found.", [System.Exception]::new("Exception of type 'ERROR_NOT_FOUND' was thrown.")) } if ([string]::IsNullOrWhiteSpace($Savd_Cred.target)) { Write-Warning "Could not resolve the target Name for: $_Target" } } end { return $Savd_Cred } } function Get-SavedCredentials { <# .SYNOPSIS Retreives All strored credentials from credential Manager .DESCRIPTION Retreives All strored credentials and returns an [System.Collections.ObjectModel.Collection[CredManaged]] object .NOTES This function is supported on windows only .LINK https://github.com/alainQtec/cliHelper.Core .EXAMPLE Get-SavedCredentials Enumerates all SavedCredentials #> [CmdletBinding()] [outputType([System.Collections.ObjectModel.Collection[CredManaged]])] param () begin { $Credentials = $null $CredentialManager = [CredentialManager]::new(); } process { $Credentials = $CredentialManager.RetreiveAll(); } end { return $Credentials; } } function Remove-Credential { <# .SYNOPSIS Deletes credential from Windows Credential Mandger .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES This function is supported on windows only .LINK https://github.com/alainQtec/cliHelper.Core/blob/main/Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1 .EXAMPLE Remove-Credential -Verbose #> [CmdletBinding(SupportsShouldProcess = $true)] param ( # TargetName [Parameter(Mandatory = $true)][ValidateLength(1, 32767)] [ValidateScript({ if (![string]::IsNullOrWhiteSpace($_)) { return $true } throw 'Null or WhiteSpace Inputs are not allowed.' } )][Alias('Title')] [String]$Target, [Parameter(Mandatory = $false)] [ValidateSet('Generic', 'DomainPassword', 'DomainCertificate', 'DomainVisiblePassword', 'GenericCertificate', 'DomainExtended', 'Maximum', 'MaximumEx')] [String]$Type = "GENERIC" ) begin { $CredentialManager = [CredentialManager]::new(); } process { $CredType = [CredType]"$Type" if ($PSCmdlet.ShouldProcess("Removing Credential, target: $Target", '', '')) { $IsRemoved = $CredentialManager.Remove($Target, $CredType); if (!$IsRemoved) { throw 'Remove-Credential Failed. ErrorCode: 0x' + [CredentialManager]::LastErrorCode } } } } function Save-Credential { <# .SYNOPSIS Saves credential to windows credential Manager .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES This function is supported on windows only .LINK https://github.com/alainQtec/cliHelper.Core .EXAMPLE Save-Credential youtube.com/@memeL0rd memeL0rd $(Read-Host -AsSecureString -Prompt "memeLord's youtube password") #> [CmdletBinding(DefaultParameterSetName = 'uts')] param ( # title aka TargetName of the credential you want to save [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'uts')] [ValidateScript({ if (![string]::IsNullOrWhiteSpace($_)) { return $true } throw 'Null or WhiteSpace targetName is not allowed.' } )][Alias('target')] [string]$Title, # UserName [Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'uts')] [Alias('UserName')] [string]$User, # Securestring / Password [Parameter(Position = 2, Mandatory = $true, ParameterSetName = 'uts')] [ValidateNotNull()] [securestring]$SecureString, # ManagedCredential Object you want to save [Parameter(Mandatory = $true, ParameterSetName = 'MC')] [Alias('Credential')][ValidateNotNull()] [CredManaged]$Obj ) process { if ($PSCmdlet.ParameterSetName -eq 'uts') { $CredentialManager = [CredentialManager]::new(); if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('User')) { [void]$CredentialManager.SaveCredential($Title, $User, $SecureString); } else { [void]$CredentialManager.SaveCredential($Title, $SecureString); } } elseif ($PSCmdlet.ParameterSetName -eq 'MC') { $CredentialManager = [CredentialManager]::new(); [void]$CredentialManager.SaveCredential($Obj); } } } function Show-SavedCredentials { <# .SYNOPSIS Retreives All strored credentials from credential Manager, but no securestrings. (Just showing) .DESCRIPTION Retreives All strored credentials and returns a PsObject[] .NOTES This function is supported on windows only .LINK https://github.com/alainQtec/cliHelper.Core .EXAMPLE Show-SavedCredentials #> [CmdletBinding()] [outputType([PsObject[]])] [Alias('ShowCreds')] param () end { return [CredentialManager]::get_StoredCreds(); } } #endregion Local_Vault #region PasswordManagment function New-Password { <# .SYNOPSIS Creates a password string .DESCRIPTION Creates a password containing minimum of 9 characters, 1 lowercase, 1 uppercase, 1 numeric, and 1 special character. Can not exceed 999 characters .LINK https://github.com/alainQtec/cliHelper.Core/blob/main/Private/cliHelper.core.xcrypt/cliHelper.core.xcrypt.psm1 .EXAMPLE New-Password Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'No system state is being changed')] [CmdletBinding(DefaultParameterSetName = 'asSecureString')] param ( # Exact password Length. Note: The minimum length is 14 characters, Otherwise it nearly impossible to create a password under 14 characters. Youll'd be better off use a random text generator! [Parameter(Position = 0, Mandatory = $false, ParameterSetName = '__AllParameterSets')] [Alias('l')][ValidateRange(14, 999)] [int]$Length = 19, [Parameter(Mandatory = $false, ParameterSetName = '__AllParameterSets')] [switch]$StartWithLetter, [Parameter(Mandatory = $false, ParameterSetName = '__AllParameterSets')] [switch]$NoSymbols, [Parameter(Mandatory = $false, ParameterSetName = '__AllParameterSets')] [switch]$UseAmbiguousCharacters, [Parameter(Mandatory = $false, ParameterSetName = '__AllParameterSets')] [switch]$UseExtendedAscii, [Parameter(Mandatory = $false, ParameterSetName = 'PlainText')] [switch]$AsPlainText ) begin { $Pass = [string]::Empty # $params = $PSCmdlet.MyInvocation.BoundParameters } process { $Pass = [xcrypt]::GeneratePassword($Length, $StartWithLetter, $NoSymbols, $UseAmbiguousCharacters, $UseExtendedAscii); if ($PSCmdlet.ParameterSetName -eq 'asSecureString') { $pass = $Pass | xconvert ToSecurestring } } end { return $Pass } } #endregion PasswordManagment #region GithubGists function Get-Gists { <# .SYNOPSIS Gets all gists for a user .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .LINK Specify a URI to a help page, this will show when Get-Help -Online is used. .EXAMPLE Get-Gists -UserName 'alainQtec' -SecureToken (Read-Host -Prompt "Github Api Token" -AsSecureString) Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [OutputType([Gist[]])] [CmdletBinding(DefaultParameterSetName = 'ClearT')] param ( [Parameter(Position = 0, Mandatory = $true, ParameterSetName = '__AllParameterSets')] [Alias('UserName')] [string]$ownerID, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'SecureT')] [ValidateNotNullOrEmpty()] [securestring]$SecureToken, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'ClearT')] [ValidateNotNullOrEmpty()] [string]$GitHubToken ) begin { $Gists = $null; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } process { if ($PSCmdlet.ParameterSetName -eq 'ClearT') { $SecureToken = $GitHubToken | xconvert ToSecurestring } if ($null -eq [GitHub]::webSession) { [void][GitHub]::createSession($ownerID, $SecureToken) } $auth = Invoke-RestMethod -Method Get -Uri "https://api.github.com/user" -WebSession ([GitHub]::webSession) if ($auth) { $Gists = $(Invoke-RestMethod -Method Get -Uri "https://api.github.com/users/$ownerID/gists" -WebSession $([GitHub]::webSession)) | Select-Object -Property @( @{l = 'Id'; e = { $_.Id } } @{l = 'Uri'; e = { "https://gist.github.com/$($_.owner.login)/$($_.Id)" -as [uri] } } @{l = 'FLOB'; e = { $_.files } } @{l = 'IsPublic'; e = { $_.public } } @{l = 'Owner'; e = { $_.owner.login } } @{l = 'Description'; e = { $_.description } } ) | Select-Object *, @{l = 'Files'; e = { $flob = [psobject[]]::new(0); $Names = $_.FLOB | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name foreach ($n in $Names) { if (($_.FLOB."$n".raw_url -as [uri]).Segments[2] -eq $_.Id + '/') { $flob += $_.FLOB."$n" } } $flob } } -ExcludeProperty FLOB Write-Verbose "Found $($Gists.Count) gists" $Gists = foreach ($g in $Gists) { $_g = [Gist]::new() $_f = $g.Files | Select-Object *, @{l = 'Id'; e = { $g.Id } }, @{l = 'Owner'; e = { $g.Owner } }, @{l = 'IsPublic'; e = { $g.IsPublic } } $_f.Foreach({ $_g.AddFile([GistFile]::new($_)) }); $_g.Description = $g.Description $_g.IsPublic = $g.IsPublic $_g.Owner = $g.Owner $_g.Uri = $g.Uri $_g.Id = $g.Id $_g } } else { throw $Error[0] } } end { return $Gists } } function Get-GistFiles { <# .SYNOPSIS Get gist Files .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .EXAMPLE $Name = "P0t4t0_ex.ps1" $params = @{ UserName = '6r1mh04x' SecureToken = Read-Host -Prompt "Github Api Token" -AsSecureString GistId = '995856aa97ac3120cd8d92d2a6eac212' } $rawUrl = (Get-GistFiles @$params).Where({ $_.Name -eq $Name }).raw_url Using this function to Fetch raw_url of a private gist #> [OutputType([GistFile[]])] [CmdletBinding(DefaultParameterSetName = 'ClearT')] param ( [Parameter(Position = 0, Mandatory = $true, ParameterSetName = '__AllParameterSets')] [Alias('UserName')] [string]$ownerID, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'SecureT')] [ValidateNotNullOrEmpty()] [securestring]$SecureToken, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'ClearT')] [ValidateNotNullOrEmpty()] [string]$GitHubToken, [Parameter(Position = 2, Mandatory = $true, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [string]$GistId ) begin { $gistFiles = $null; } process { $gistFiles = if ($PSCmdlet.ParameterSetName -eq 'ClearT') { Get-Gists -UserName $ownerID -GitHubToken $GitHubToken } else { Get-Gists -UserName $ownerID -SecureToken $SecureToken } $gistFiles = $gistFiles.Files.Where({ $_.Id -eq $GistId } ) } end { return $gistFiles } } #endregion GithubGistss #endregion Functions |