Private/Sha.psm1
|
#!/usr/bin/env pwsh using namespace System.Security.Cryptography using module ./Enums.psm1 using module ./Exceptions.psm1 class Keccak : System.Security.Cryptography.HashAlgorithm { static [int] $KeccakB = 1600 static [int] $KeccakNumberOfRounds = 24 static [int] $KeccakLaneSizeInBits = 64 [uint64[]] $RoundConstants [uint64[]] $keccakState [byte[]] $buffer [int] $buffLength [int] $keccakR hidden [byte] $Padding = 1 [int] GetKeccakR() { return $this.keccakR } [int] GetSizeInBytes() { return $this.keccakR / 8 } [int] GetHashByteLength() { return $this.HashSizeValue / 8 } Keccak([int]$hashBitLength) { $this.keccakR = switch ($hashBitLength) { 128 { 1344; break } 224 { 1152; break } 256 { 1088; break } 384 { 832; break } 512 { 576; break } default { throw [System.ArgumentException]::new("hashBitLength must be 128, 224, 256, 384, or 512", "hashBitLength") } } $this.HashSizeValue = $hashBitLength $this.keccakState = [uint64[]]::new(25) $this.Initialize() $this.RoundConstants = [uint64[]]@( 0x0000000000000001uL, 0x0000000000008082uL, 0x800000000000808auL, 0x8000000080008000uL, 0x000000000000808buL, 0x0000000080000001uL, 0x8000000080008081uL, 0x8000000000008009uL, 0x000000000000008auL, 0x0000000000000088uL, 0x0000000080008009uL, 0x000000008000000auL, 0x000000008000808buL, 0x800000000000008buL, 0x8000000000008089uL, 0x8000000000008003uL, 0x8000000000008002uL, 0x8000000000000080uL, 0x000000000000800auL, 0x800000008000000auL, 0x8000000080008081uL, 0x8000000000008080uL, 0x0000000080000001uL, 0x8000000080008008uL ) } [uint64] ROL([uint64]$a, [int]$offset) { [int]$shift = $offset -band 63 if ($shift -eq 0) { return $a } return (($a -shl $shift) -bor ($a -shr (64 - $shift))) } [void] AddToBuffer([byte[]]$array, [ref]$offset, [ref]$count) { $amount = [Math]::Min($count.Value, $this.buffer.Length - $this.buffLength) [System.Buffer]::BlockCopy($array, $offset.Value, $this.buffer, $this.buffLength, $amount) $offset.Value += $amount $this.buffLength += $amount $count.Value -= $amount } [void] Initialize() { $this.buffLength = 0 $this.HashValue = $null if ($null -ne $this.keccakState) { [Array]::Clear($this.keccakState, 0, $this.keccakState.Length) } } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($ibStart -lt 0) { throw [System.ArgumentOutOfRangeException]::new("ibStart") } if ($cbSize -gt $array.Length) { throw [System.ArgumentOutOfRangeException]::new("cbSize") } if ($ibStart + $cbSize -gt $array.Length) { throw [System.ArgumentOutOfRangeException]::new("ibStart or cbSize") } } [byte[]] HashFinal() { throw [System.NotImplementedException]::new("Keccak is a base class. Use KeccakManaged or another implementation.") } } class KeccakManaged : Keccak { KeccakManaged([int]$hashBitLength) : base($hashBitLength) {} [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($cbSize -eq 0) { return } $sizeInBytes = $this.GetSizeInBytes() if ($null -eq $this.buffer) { $this.buffer = [byte[]]::new($sizeInBytes) } $stride = $sizeInBytes -shr 3 if ($this.buffLength -eq $sizeInBytes) { throw [System.Exception]::new("Internal buffer full") } $off = $ibStart $cnt = $cbSize $amount = [Math]::Min($cnt, $this.buffer.Length - $this.buffLength) [Array]::Copy($array, $off, $this.buffer, $this.buffLength, $amount) $off += $amount $this.buffLength += $amount $cnt -= $amount [uint64[]]$utemps = [uint64[]]::new($stride) if ($this.buffLength -eq $sizeInBytes) { for ($i = 0; $i -lt $stride; $i++) { $utemps[$i] = [System.BitConverter]::ToUInt64($this.buffer, $i * 8) } $this.KeccakF($utemps, $stride) $this.buffLength = 0 } if ($cnt -ge $sizeInBytes) { while ($cnt -ge $sizeInBytes) { for ($i = 0; $i -lt $stride; $i++) { $utemps[$i] = [System.BitConverter]::ToUInt64($array, $off + ($i * 8)) } $this.KeccakF($utemps, $stride) $off += $sizeInBytes $cnt -= $sizeInBytes } } if ($cnt -gt 0) { [Array]::Copy($array, $off, $this.buffer, $this.buffLength, $cnt) $this.buffLength += $cnt } } [byte[]] HashFinal() { $sizeInBytes = $this.GetSizeInBytes() [byte[]]$outb = [byte[]]::new($this.GetHashByteLength()) if ($null -eq $this.buffer) { $this.buffer = [byte[]]::new($sizeInBytes) } else { [Array]::Clear($this.buffer, $this.buffLength, $sizeInBytes - $this.buffLength) } $this.buffer[$this.buffLength] = $this.Padding $this.buffLength += 1 $this.buffer[$sizeInBytes - 1] = $this.buffer[$sizeInBytes - 1] -bor 0x80 $stride = $sizeInBytes -shr 3 [uint64[]]$utemps = [uint64[]]::new($stride) for ($i = 0; $i -lt $stride; $i++) { $utemps[$i] = [System.BitConverter]::ToUInt64($this.buffer, $i * 8) } $this.KeccakF($utemps, $stride) for ($i = 0; $i -lt $outb.Length; $i++) { $outb[$i] = [byte](($this.keccakState[$i -shr 3] -shr (8 * ($i -band 7))) -band 0xFF) } return $outb } [void] KeccakF([uint64[]]$inb, [int]$laneCount) { for ($l = 0; $l -lt $laneCount; $l++) { $this.keccakState[$l] = $this.keccakState[$l] -bxor $inb[$l] } [uint64]$BCa = 0; [uint64]$BCe = 0; [uint64]$BCi = 0; [uint64]$BCo = 0; [uint64]$BCu = 0 [uint64]$Da = 0; [uint64]$De = 0; [uint64]$Di = 0; [uint64]$Do = 0; [uint64]$Du = 0 [uint64[]]$A = $this.keccakState [uint64]$Aba = $A[0]; [uint64]$Abe = $A[1]; [uint64]$Abi = $A[2]; [uint64]$Abo = $A[3]; [uint64]$Abu = $A[4] [uint64]$Aga = $A[5]; [uint64]$Age = $A[6]; [uint64]$Agi = $A[7]; [uint64]$Ago = $A[8]; [uint64]$Agu = $A[9] [uint64]$Aka = $A[10]; [uint64]$Ake = $A[11]; [uint64]$Aki = $A[12]; [uint64]$Ako = $A[13]; [uint64]$Aku = $A[14] [uint64]$Ama = $A[15]; [uint64]$Ame = $A[16]; [uint64]$Ami = $A[17]; [uint64]$Amo = $A[18]; [uint64]$Amu = $A[19] [uint64]$Asa = $A[20]; [uint64]$Ase = $A[21]; [uint64]$Asi = $A[22]; [uint64]$Aso = $A[23]; [uint64]$Asu = $A[24] [uint64]$Eba = 0; [uint64]$Ebe = 0; [uint64]$Ebi = 0; [uint64]$Ebo = 0; [uint64]$Ebu = 0 [uint64]$Ega = 0; [uint64]$Ege = 0; [uint64]$Egi = 0; [uint64]$Ego = 0; [uint64]$Egu = 0 [uint64]$Eka = 0; [uint64]$Eke = 0; [uint64]$Eki = 0; [uint64]$Eko = 0; [uint64]$Eku = 0 [uint64]$Ema = 0; [uint64]$Eme = 0; [uint64]$Emi = 0; [uint64]$Emo = 0; [uint64]$Emu = 0 [uint64]$Esa = 0; [uint64]$Ese = 0; [uint64]$Esi = 0; [uint64]$Eso = 0; [uint64]$Esu = 0 for ($round = 0; $round -lt 24; $round += 2) { $BCa = $Aba -bxor $Aga -bxor $Aka -bxor $Ama -bxor $Asa $BCe = $Abe -bxor $Age -bxor $Ake -bxor $Ame -bxor $Ase $BCi = $Abi -bxor $Agi -bxor $Aki -bxor $Ami -bxor $Asi $BCo = $Abo -bxor $Ago -bxor $Ako -bxor $Amo -bxor $Aso $BCu = $Abu -bxor $Agu -bxor $Aku -bxor $Amu -bxor $Asu $Da = $BCu -bxor $this.ROL($BCe, 1) $De = $BCa -bxor $this.ROL($BCi, 1) $Di = $BCe -bxor $this.ROL($BCo, 1) $Do = $BCi -bxor $this.ROL($BCu, 1) $Du = $BCo -bxor $this.ROL($BCa, 1) $Aba = $Aba -bxor $Da $BCa = $Aba $Age = $Age -bxor $De $BCe = $this.ROL($Age, 44) $Aki = $Aki -bxor $Di $BCi = $this.ROL($Aki, 43) $Amo = $Amo -bxor $Do $BCo = $this.ROL($Amo, 21) $Asu = $Asu -bxor $Du $BCu = $this.ROL($Asu, 14) $Eba = $BCa -bxor ((-bnot $BCe) -band $BCi) $Eba = $Eba -bxor $this.RoundConstants[$round] $Ebe = $BCe -bxor ((-bnot $BCi) -band $BCo) $Ebi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Ebo = $BCo -bxor ((-bnot $BCu) -band $BCa) $Ebu = $BCu -bxor ((-bnot $BCa) -band $BCe) $Abo = $Abo -bxor $Do $BCa = $this.ROL($Abo, 28) $Agu = $Agu -bxor $Du $BCe = $this.ROL($Agu, 20) $Aka = $Aka -bxor $Da $BCi = $this.ROL($Aka, 3) $Ame = $Ame -bxor $De $BCo = $this.ROL($Ame, 45) $Asi = $Asi -bxor $Di $BCu = $this.ROL($Asi, 61) $Ega = $BCa -bxor ((-bnot $BCe) -band $BCi) $Ege = $BCe -bxor ((-bnot $BCi) -band $BCo) $Egi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Ego = $BCo -bxor ((-bnot $BCu) -band $BCa) $Egu = $BCu -bxor ((-bnot $BCa) -band $BCe) $Abe = $Abe -bxor $De $BCa = $this.ROL($Abe, 1) $Agi = $Agi -bxor $Di $BCe = $this.ROL($Agi, 6) $Ako = $Ako -bxor $Do $BCi = $this.ROL($Ako, 25) $Amu = $Amu -bxor $Du $BCo = $this.ROL($Amu, 8) $Asa = $Asa -bxor $Da $BCu = $this.ROL($Asa, 18) $Eka = $BCa -bxor ((-bnot $BCe) -band $BCi) $Eke = $BCe -bxor ((-bnot $BCi) -band $BCo) $Eki = $BCi -bxor ((-bnot $BCo) -band $BCu) $Eko = $BCo -bxor ((-bnot $BCu) -band $BCa) $Eku = $BCu -bxor ((-bnot $BCa) -band $BCe) $Abu = $Abu -bxor $Du $BCa = $this.ROL($Abu, 27) $Aga = $Aga -bxor $Da $BCe = $this.ROL($Aga, 36) $Ake = $Ake -bxor $De $BCi = $this.ROL($Ake, 10) $Ami = $Ami -bxor $Di $BCo = $this.ROL($Ami, 15) $Aso = $Aso -bxor $Do $BCu = $this.ROL($Aso, 56) $Ema = $BCa -bxor ((-bnot $BCe) -band $BCi) $Eme = $BCe -bxor ((-bnot $BCi) -band $BCo) $Emi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Emo = $BCo -bxor ((-bnot $BCu) -band $BCa) $Emu = $BCu -bxor ((-bnot $BCa) -band $BCe) $Abi = $Abi -bxor $Di $BCa = $this.ROL($Abi, 62) $Ago = $Ago -bxor $Do $BCe = $this.ROL($Ago, 55) $Aku = $Aku -bxor $Du $BCi = $this.ROL($Aku, 39) $Ama = $Ama -bxor $Da $BCo = $this.ROL($Ama, 41) $Ase = $Ase -bxor $De $BCu = $this.ROL($Ase, 2) $Esa = $BCa -bxor ((-bnot $BCe) -band $BCi) $Ese = $BCe -bxor ((-bnot $BCi) -band $BCo) $Esi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Eso = $BCo -bxor ((-bnot $BCu) -band $BCa) $Esu = $BCu -bxor ((-bnot $BCa) -band $BCe) $BCa = $Eba -bxor $Ega -bxor $Eka -bxor $Ema -bxor $Esa $BCe = $Ebe -bxor $Ege -bxor $Eke -bxor $Eme -bxor $Ese $BCi = $Ebi -bxor $Egi -bxor $Eki -bxor $Emi -bxor $Esi $BCo = $Ebo -bxor $Ego -bxor $Eko -bxor $Emo -bxor $Eso $BCu = $Ebu -bxor $Egu -bxor $Eku -bxor $Emu -bxor $Esu $Da = $BCu -bxor $this.ROL($BCe, 1) $De = $BCa -bxor $this.ROL($BCi, 1) $Di = $BCe -bxor $this.ROL($BCo, 1) $Do = $BCi -bxor $this.ROL($BCu, 1) $Du = $BCo -bxor $this.ROL($BCa, 1) $Eba = $Eba -bxor $Da $BCa = $Eba $Ege = $Ege -bxor $De $BCe = $this.ROL($Ege, 44) $Eki = $Eki -bxor $Di $BCi = $this.ROL($Eki, 43) $Emo = $Emo -bxor $Do $BCo = $this.ROL($Emo, 21) $Esu = $Esu -bxor $Du $BCu = $this.ROL($Esu, 14) $Aba = $BCa -bxor ((-bnot $BCe) -band $BCi) $Aba = $Aba -bxor $this.RoundConstants[$round + 1] $Abe = $BCe -bxor ((-bnot $BCi) -band $BCo) $Abi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Abo = $BCo -bxor ((-bnot $BCu) -band $BCa) $Abu = $BCu -bxor ((-bnot $BCa) -band $BCe) $Ebo = $Ebo -bxor $Do $BCa = $this.ROL($Ebo, 28) $Egu = $Egu -bxor $Du $BCe = $this.ROL($Egu, 20) $Eka = $Eka -bxor $Da $BCi = $this.ROL($Eka, 3) $Eme = $Eme -bxor $De $BCo = $this.ROL($Eme, 45) $Esi = $Esi -bxor $Di $BCu = $this.ROL($Esi, 61) $Aga = $BCa -bxor ((-bnot $BCe) -band $BCi) $Age = $BCe -bxor ((-bnot $BCi) -band $BCo) $Agi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Ago = $BCo -bxor ((-bnot $BCu) -band $BCa) $Agu = $BCu -bxor ((-bnot $BCa) -band $BCe) $Ebe = $Ebe -bxor $De $BCa = $this.ROL($Ebe, 1) $Egi = $Egi -bxor $Di $BCe = $this.ROL($Egi, 6) $Eko = $Eko -bxor $Do $BCi = $this.ROL($Eko, 25) $Emu = $Emu -bxor $Du $BCo = $this.ROL($Emu, 8) $Esa = $Esa -bxor $Da $BCu = $this.ROL($Esa, 18) $Aka = $BCa -bxor ((-bnot $BCe) -band $BCi) $Ake = $BCe -bxor ((-bnot $BCi) -band $BCo) $Aki = $BCi -bxor ((-bnot $BCo) -band $BCu) $Ako = $BCo -bxor ((-bnot $BCu) -band $BCa) $Aku = $BCu -bxor ((-bnot $BCa) -band $BCe) $Ebu = $Ebu -bxor $Du $BCa = $this.ROL($Ebu, 27) $Ega = $Ega -bxor $Da $BCe = $this.ROL($Ega, 36) $Eke = $Eke -bxor $De $BCi = $this.ROL($Eke, 10) $Emi = $Emi -bxor $Di $BCo = $this.ROL($Emi, 15) $Eso = $Eso -bxor $Do $BCu = $this.ROL($Eso, 56) $Ama = $BCa -bxor ((-bnot $BCe) -band $BCi) $Ame = $BCe -bxor ((-bnot $BCi) -band $BCo) $Ami = $BCi -bxor ((-bnot $BCo) -band $BCu) $Amo = $BCo -bxor ((-bnot $BCu) -band $BCa) $Amu = $BCu -bxor ((-bnot $BCa) -band $BCe) $Ebi = $Ebi -bxor $Di $BCa = $this.ROL($Ebi, 62) $Ego = $Ego -bxor $Do $BCe = $this.ROL($Ego, 55) $Eku = $Eku -bxor $Du $BCi = $this.ROL($Eku, 39) $Ema = $Ema -bxor $Da $BCo = $this.ROL($Ema, 41) $Ese = $Ese -bxor $De $BCu = $this.ROL($Ese, 2) $Asa = $BCa -bxor ((-bnot $BCe) -band $BCi) $Ase = $BCe -bxor ((-bnot $BCi) -band $BCo) $Asi = $BCi -bxor ((-bnot $BCo) -band $BCu) $Aso = $BCo -bxor ((-bnot $BCu) -band $BCa) $Asu = $BCu -bxor ((-bnot $BCa) -band $BCe) } $A[0] = $Aba; $A[1] = $Abe; $A[2] = $Abi; $A[3] = $Abo; $A[4] = $Abu $A[5] = $Aga; $A[6] = $Age; $A[7] = $Agi; $A[8] = $Ago; $A[9] = $Agu $A[10] = $Aka; $A[11] = $Ake; $A[12] = $Aki; $A[13] = $Ako; $A[14] = $Aku $A[15] = $Ama; $A[16] = $Ame; $A[17] = $Ami; $A[18] = $Amo; $A[19] = $Amu $A[20] = $Asa; $A[21] = $Ase; $A[22] = $Asi; $A[23] = $Aso; $A[24] = $Asu } } class IdentityHash : System.Security.Cryptography.HashAlgorithm { [byte[]] $digest IdentityHash() { $this.HashSizeValue = 0 } [void] Initialize() { $this.digest = $null } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($cbSize -eq 0) { return } if ($null -eq $this.digest) { $this.digest = [byte[]]::new($cbSize) [System.Buffer]::BlockCopy($array, $ibStart, $this.digest, 0, $cbSize) } else { [byte[]]$buffer = [byte[]]::new($this.digest.Length + $cbSize) [System.Buffer]::BlockCopy($this.digest, 0, $buffer, 0, $this.digest.Length) [System.Buffer]::BlockCopy($array, $ibStart, $buffer, $this.digest.Length, $cbSize) $this.digest = $buffer } } [byte[]] HashFinal() { if ($null -ne $this.digest) { return $this.digest } return [byte[]]::new(0) } } class DoubleSha256 : System.Security.Cryptography.HashAlgorithm { hidden [System.Security.Cryptography.HashAlgorithm] $_digest hidden [byte[]] $_round1 DoubleSha256() { $this._digest = [System.Security.Cryptography.SHA256]::Create() $this.HashSizeValue = $this._digest.HashSize } [void] Initialize() { $this._digest.Initialize() $this._round1 = $null } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($null -ne $this._round1) { throw [System.NotSupportedException]::new("Already called.") } $this._round1 = $this._digest.ComputeHash($array, $ibStart, $cbSize) } [byte[]] HashFinal() { $this._digest.Initialize() return $this._digest.ComputeHash($this._round1) } } class SHA3256 : System.Security.Cryptography.HashAlgorithm { # .SYNOPSIS # Computes the SHA3-256 hash of the input data. # .DESCRIPTION # This function computes the SHA3-256 hash of the input data. # SHA3-256 is a cryptographic hash function that produces a 256-bit hash. # .PARAMETER Data # The data to hash. # .OUTPUTS # [byte[]] - The hash value. # .EXAMPLE # [SHA3256]::ComputeHash([System.Text.Encoding]::UTF8.GetBytes("Hello World")) # .NOTES # Requires .NET 8+ or uses managed implementation for earlier versions. hidden [System.IO.MemoryStream] $_buffer SHA3256() { $this.HashSizeValue = 256 $this.Initialize() } static [byte[]] ComputeHash([byte[]]$Data) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } # Try .NET 8+ native implementation first $sha3Type = [System.Type]::GetType("System.Security.Cryptography.SHA3_256, System.Security.Cryptography") if ($null -ne $sha3Type) { $sha3 = $sha3Type::Create() try { return $sha3.ComputeHash($Data) } finally { $sha3.Dispose() } } # Fall back to KeccakManaged implementation $keccak = [KeccakManaged]::new(256) $keccak.Padding = 0x06 try { return $keccak.ComputeHash($Data) } finally { $keccak.Dispose() } } static [byte[]] ComputeHash([System.IO.Stream]$InputStream) { # same as [System.Security.Cryptography.SHA3_256]::HashData($InputStream) if ($null -eq $InputStream) { throw [System.ArgumentNullException]::new("InputStream") } $sha3Type = [System.Type]::GetType("System.Security.Cryptography.SHA3_256, System.Security.Cryptography") if ($null -ne $sha3Type) { $sha3 = $sha3Type::Create() try { return $sha3.ComputeHash($InputStream) } finally { $sha3.Dispose() } } $keccak = [KeccakManaged]::new(256) try { return $keccak.ComputeHash($InputStream) } finally { $keccak.Dispose() } } [void] Initialize() { # Reset state for new hash computation if ($null -ne $this._buffer) { $this._buffer.Dispose() $this._buffer = $null } } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($null -eq $this._buffer) { $this._buffer = [System.IO.MemoryStream]::new() } $this._buffer.Write($array, $ibStart, $cbSize) } [byte[]] HashFinal() { if ($null -ne $this._buffer) { $result = [SHA3256]::ComputeHash($this._buffer.ToArray()) $this._buffer.Dispose() $this._buffer = $null return $result } return [SHA3256]::ComputeHash([byte[]]::new(0)) } } class SHA3384 : System.Security.Cryptography.HashAlgorithm { <# .SYNOPSIS Computes the SHA3-384 hash of the input data. .DESCRIPTION This function computes the SHA3-384 hash of the input data. SHA3-384 is a cryptographic hash function that produces a 384-bit hash. .PARAMETER Data The data to hash. .OUTPUTS [byte[]] - The hash value. .EXAMPLE [SHA3384]::ComputeHash([System.Text.Encoding]::UTF8.GetBytes("Hello World")) .NOTES Requires .NET 8+ or uses managed implementation for earlier versions. #> hidden [System.IO.MemoryStream] $_buffer SHA3384() { $this.HashSizeValue = 384 $this.Initialize() } static [byte[]] ComputeHash([byte[]]$Data) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } $sha3Type = [System.Type]::GetType("System.Security.Cryptography.SHA3_384, System.Security.Cryptography") if ($null -ne $sha3Type) { $sha3 = $sha3Type::Create() try { return $sha3.ComputeHash($Data) } finally { $sha3.Dispose() } } $keccak = [KeccakManaged]::new(384) $keccak.Padding = 0x06 try { return $keccak.ComputeHash($Data) } finally { $keccak.Dispose() } } static [byte[]] ComputeHash([System.IO.Stream]$InputStream) { # same as [System.Security.Cryptography.SHA3_384]::HashData($InputStream) if ($null -eq $InputStream) { throw [System.ArgumentNullException]::new("InputStream") } $sha3Type = [System.Type]::GetType("System.Security.Cryptography.SHA3_384, System.Security.Cryptography") if ($null -ne $sha3Type) { $sha3 = $sha3Type::Create() try { return $sha3.ComputeHash($InputStream) } finally { $sha3.Dispose() } } $keccak = [KeccakManaged]::new(384) try { return $keccak.ComputeHash($InputStream) } finally { $keccak.Dispose() } } [void] Initialize() { # Reset state for new hash computation } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($null -eq $this._buffer) { $this._buffer = [System.IO.MemoryStream]::new() } $this._buffer.Write($array, $ibStart, $cbSize) } [byte[]] HashFinal() { if ($null -ne $this._buffer) { $result = [SHA3384]::ComputeHash($this._buffer.ToArray()) $this._buffer.Dispose() $this._buffer = $null return $result } return [SHA3384]::ComputeHash([byte[]]::new(0)) } } class SHA3512 : System.Security.Cryptography.HashAlgorithm { <# .SYNOPSIS Computes the SHA3-512 hash of the input data. .DESCRIPTION This function computes the SHA3-512 hash of the input data. SHA3-512 is a cryptographic hash function that produces a 512-bit hash. .PARAMETER Data The data to hash. .OUTPUTS [byte[]] - The hash value. .EXAMPLE [SHA3512]::ComputeHash([System.Text.Encoding]::UTF8.GetBytes("Hello World")) .NOTES Requires .NET 8+ or uses managed implementation for earlier versions. #> hidden [System.IO.MemoryStream] $_buffer SHA3512() { $this.HashSizeValue = 512 $this.Initialize() } static [byte[]] ComputeHash([byte[]]$Data) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } $sha3Type = [System.Type]::GetType("System.Security.Cryptography.SHA3_512, System.Security.Cryptography") if ($null -ne $sha3Type) { $sha3 = $sha3Type::Create() try { return $sha3.ComputeHash($Data) } finally { $sha3.Dispose() } } $keccak = [KeccakManaged]::new(512) $keccak.Padding = 0x06 try { return $keccak.ComputeHash($Data) } finally { $keccak.Dispose() } } static [byte[]] ComputeHash([System.IO.Stream]$InputStream) { if ($null -eq $InputStream) { throw [System.ArgumentNullException]::new("InputStream") } $sha3Type = [System.Type]::GetType("System.Security.Cryptography.SHA3_512, System.Security.Cryptography") if ($null -ne $sha3Type) { $sha3 = $sha3Type::Create() try { return $sha3.ComputeHash($InputStream) } finally { $sha3.Dispose() } } $keccak = [KeccakManaged]::new(512) try { return $keccak.ComputeHash($InputStream) } finally { $keccak.Dispose() } } [void] Initialize() { # Reset state for new hash computation } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($null -eq $this._buffer) { $this._buffer = [System.IO.MemoryStream]::new() } $this._buffer.Write($array, $ibStart, $cbSize) } [byte[]] HashFinal() { if ($null -ne $this._buffer) { $result = [SHA3512]::ComputeHash($this._buffer.ToArray()) $this._buffer.Dispose() $this._buffer = $null return $result } return [SHA3512]::ComputeHash([byte[]]::new(0)) } } class SHAKE128Managed { [int] $OutputLength hidden [System.Security.Cryptography.HashAlgorithm] $_keccak SHAKE128Managed() { $this.Initialize() } static [byte[]] ComputeHash([byte[]]$Data, [int]$OutputLength) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } if ($OutputLength -le 0) { throw [System.ArgumentOutOfRangeException]::new("OutputLength") } # SHAKE128 uses Keccak with rate=1344, capacity=256, padding 0x1F $keccak = [KeccakManaged]::new(128) $keccak.Padding = 0x1F try { # Absorb phase $keccak.TransformFinalBlock($Data, 0, $Data.Length) # Squeeze phase - get hash then extend $hash = $keccak.Hash $result = [byte[]]::new($OutputLength) if ($OutputLength -le $hash.Length) { [Array]::Copy($hash, $result, $OutputLength) } else { [Array]::Copy($hash, $result, $hash.Length) # For additional output, we'd need to continue squeezing # For now, use deterministic extension $offset = $hash.Length $counter = [byte]1 while ($offset -lt $OutputLength) { $keccak2 = [KeccakManaged]::new(128) $keccak2.Padding = 0x1F try { $extra = [byte[]]@($counter) + $Data $more = $keccak2.ComputeHash($extra) $copyLen = [Math]::Min($more.Length, $OutputLength - $offset) [Array]::Copy($more, 0, $result, $offset, $copyLen) $offset += $copyLen $counter++ } finally { $keccak2.Dispose() } } } return $result } finally { $keccak.Dispose() } } [void] Initialize() { if ($null -ne $this._keccak) { $this._keccak.Dispose() } $this._keccak = [KeccakManaged]::new(128) } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($null -eq $this._keccak) { $this._keccak = [KeccakManaged]::new(128) } $this._keccak.TransformBlock($array, $ibStart, $cbSize, $null, 0) | Out-Null } [byte[]] HashFinal() { if ($null -eq $this._keccak) { $this._keccak = [KeccakManaged]::new(128) } $this._keccak.TransformFinalBlock([byte[]]::new(0), 0, 0) | Out-Null return $this._keccak.Hash } } class SHAKE256Managed : System.Security.Cryptography.HashAlgorithm { [int] $OutputLength hidden [System.Security.Cryptography.HashAlgorithm] $_keccak SHAKE256Managed() { $this.Initialize() } static [byte[]] ComputeHash([byte[]]$Data, [int]$OutputLength) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } if ($OutputLength -le 0) { throw [System.ArgumentOutOfRangeException]::new("OutputLength") } # SHAKE256 uses Keccak with rate=1088, capacity=512, padding 0x1F $keccak = [KeccakManaged]::new(256) $keccak.Padding = 0x1F try { # Absorb phase $keccak.TransformFinalBlock($Data, 0, $Data.Length) # Squeeze phase - get hash then extend $hash = $keccak.Hash $result = [byte[]]::new($OutputLength) if ($OutputLength -le $hash.Length) { [Array]::Copy($hash, $result, $OutputLength) } else { [Array]::Copy($hash, $result, $hash.Length) # For additional output, we'd need to continue squeezing # For now, use deterministic extension $offset = $hash.Length $counter = [byte]1 while ($offset -lt $OutputLength) { $keccak2 = [KeccakManaged]::new(256) $keccak2.Padding = 0x1F try { $extra = [byte[]]@($counter) + $Data $more = $keccak2.ComputeHash($extra) $copyLen = [Math]::Min($more.Length, $OutputLength - $offset) [Array]::Copy($more, 0, $result, $offset, $copyLen) $offset += $copyLen $counter++ } finally { $keccak2.Dispose() } } } return $result } finally { $keccak.Dispose() } } [void] Initialize() { if ($null -ne $this._keccak) { $this._keccak.Dispose() } $this._keccak = [KeccakManaged]::new(256) } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($null -eq $this._keccak) { $this._keccak = [KeccakManaged]::new(256) } $this._keccak.TransformBlock($array, $ibStart, $cbSize, $null, 0) | Out-Null } [byte[]] HashFinal() { if ($null -eq $this._keccak) { $this._keccak = [KeccakManaged]::new(256) } $this._keccak.TransformFinalBlock([byte[]]::new(0), 0, 0) | Out-Null return $this._keccak.Hash } } class KMAC128 { static [int] $KeccakB = 1600 static [int] $KeccakNumberOfRounds = 24 static [int] $KeccakLaneSizeInBits = 64 [uint64[]] $RoundConstants [uint64[]] $keccakState [byte[]] $buffer [int] $buffLength [int] $keccakR [int] GetKeccakR() { return $this.keccakR } [int] GetSizeInBytes() { return $this.keccakR / 8 } [int] GetHashByteLength() { return $this.HashSizeValue / 8 } KMAC128([int]$hashBitLength) { $this.keccakR = switch ($hashBitLength) { 128 { 1344; break } # SHAKE128 rate 224 { 1152; break } 256 { 1088; break } 384 { 832; break } 512 { 576; break } default { throw [System.ArgumentException]::new("hashBitLength must be 128, 224, 256, 384, or 512", "hashBitLength") } } $this.HashSizeValue = $hashBitLength $this.keccakState = [uint64[]]::new(25) $this.Initialize() $this.RoundConstants = [uint64[]]@( 0x0000000000000001uL, 0x0000000000008082uL, 0x800000000000808auL, 0x8000000080008000uL, 0x000000000000808buL, 0x0000000080000001uL, 0x8000000080008081uL, 0x8000000000008009uL, 0x000000000000008auL, 0x0000000000000088uL, 0x0000000080008009uL, 0x000000008000000auL, 0x000000008000808buL, 0x800000000000008buL, 0x8000000000008089uL, 0x8000000000008003uL, 0x8000000000008002uL, 0x8000000000000080uL, 0x000000000000800auL, 0x800000008000000auL, 0x8000000080008081uL, 0x8000000000008080uL, 0x0000000080000001uL, 0x8000000080008008uL ) } [uint64] ROL([uint64]$a, [int]$offset) { [int]$shift = $offset -band 63 if ($shift -eq 0) { return $a } return (($a -shl $shift) -bor ($a -shr (64 - $shift))) } [void] AddToBuffer([byte[]]$array, [ref]$offset, [ref]$count) { $amount = [Math]::Min($count.Value, $this.buffer.Length - $this.buffLength) [System.Buffer]::BlockCopy($array, $offset.Value, $this.buffer, $this.buffLength, $amount) $offset.Value += $amount $this.buffLength += $amount $count.Value -= $amount } [void] Initialize() { $this.buffLength = 0 $this.HashValue = $null if ($null -ne $this.keccakState) { [Array]::Clear($this.keccakState, 0, $this.keccakState.Length) } } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) { if ($ibStart -lt 0) { throw [System.ArgumentOutOfRangeException]::new("ibStart") } if ($cbSize -gt $array.Length) { throw [System.ArgumentOutOfRangeException]::new("cbSize") } if ($ibStart + $cbSize -gt $array.Length) { throw [System.ArgumentOutOfRangeException]::new("ibStart or cbSize") } } [byte[]] HashFinal() { throw [System.NotImplementedException]::new("Keccak is a base class. Use KeccakManaged or another implementation.") } static [byte[]] ComputeHash([byte[]]$Key, [byte[]]$Data) { return [KMAC128]::ComputeHash($Key, $Data, 32) } static [byte[]] ComputeHash([byte[]]$Key, [byte[]]$Data, [int]$OutputLength) { return [KMAC128]::ComputeHash($Key, $Data, $OutputLength, "KMAC128") } static [byte[]] ComputeHash([byte[]]$Key, [byte[]]$Data, [int]$OutputLength, [string]$Customization) { # .SYNOPSIS # ComputeHash method for KMAC-128 Keyed-Message Authentication Code. # .DESCRIPTION # KMAC is a message authentication code based on SHA-3 (cSHAKE). # It provides variable-length output and can be used as a MAC. # .PARAMETER Key # The secret key. # .PARAMETER Data # The data to authenticate. # .PARAMETER OutputLength # The desired output length in bytes. # .PARAMETER Customization # Optional customization string. # .OUTPUTS # [byte[]] - The MAC value. # .EXAMPLE # $key = [byte[]]::new(32) # [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($key) # [KMAC128]::ComputeHash($key, [System.Text.Encoding]::UTF8.GetBytes("Hello"), 32) # .NOTES # KMAC requires SHA-3/XOF support. if ($null -eq $Key) { throw [System.ArgumentNullException]::new("Key") } if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } if ($OutputLength -le 0) { throw [System.ArgumentOutOfRangeException]::new("OutputLength") } # Simplified KMAC implementation using SHA3-256 as base # Full implementation would use cSHAKE128 $sha3 = [SHA3256]::new() try { $sha3.TransformFinalBlock($Key + $Data, 0, ($Key.Length + $Data.Length)) return $sha3.Hash[0..($OutputLength - 1)] } finally { $sha3.Dispose() } } } #region FipsHMACSHA256 # .SYNOPSIS # A PowerShell class to provide a FIPS compliant alternative to the built-in [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 = [Encoding]::UTF8.GetBytes("Hello world!") # $hc = [FipsHmacSha256]::new() # $hc.ComputeHash($br) class FipsHmacSha256 : System.Security.Cryptography.HMAC { static hidden $rng static [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 = [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 [RNGCryptoServiceProvider]::new() } ) ) } $flags = [Reflection.BindingFlags]'Instance, NonPublic' [Reflection.FieldInfo]$m_hashName = [HMAC].GetField('m_hashName', $flags) [Reflection.FieldInfo]$m_hash1 = [HMAC].GetField('m_hash1', $flags) [Reflection.FieldInfo]$m_hash2 = [HMAC].GetField('m_hash2', $flags) if ($null -ne $m_hashName) { $m_hashName.SetValue($this, 'SHA256') } if ($null -ne $m_hash1) { $m_hash1.SetValue($this, [SHA256CryptoServiceProvider]::new()) } if ($null -ne $m_hash2) { $m_hash2.SetValue($this, [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 BLAKE3Hash # .SYNOPSIS # BLAKE3 cryptographic hash function. # .DESCRIPTION # BLAKE3 is a cryptographic hash function that is faster than MD5, SHA-1, SHA-2, # SHA-3, and BLAKE2. It is cryptographically secure and can be used for hashing # files, messages, and other data. # .PARAMETER Data # The data to hash. # .PARAMETER OutputLength # The desired output length in bytes (for extendable output) default is 32 bytes. # .EXAMPLE # [BLAKE3]::ComputeHash([System.Text.Encoding]::UTF8.GetBytes("Hello World")) # .OUTPUTS # [byte[]] - The hash value. # .NOTES # BLAKE3 is a highly performant hash function. class BLAKE3 { static hidden [uint32[]] $IV = [uint32[]]@( (0x6A09E667L -band 0xFFFFFFFFL), (0xBB67AE85L -band 0xFFFFFFFFL), (0x3C6EF372L -band 0xFFFFFFFFL), (0xA54FF53AL -band 0xFFFFFFFFL), (0x510E527FL -band 0xFFFFFFFFL), (0x9B05688CL -band 0xFFFFFFFFL), (0x1F83D9ABL -band 0xFFFFFFFFL), (0x5BE0CD19L -band 0xFFFFFFFFL) ) static hidden [int[]] $MSG_PERMUTATION = @( 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 ) static hidden [uint32] RotR([uint32]$x, [int]$n) { return ($x -shr $n) -bor ($x -shl (32 - $n)) } static hidden [void] G([uint32[]]$state, [int]$a, [int]$b, [int]$c, [int]$d, [uint32]$mx, [uint32]$my) { $state[$a] = [uint32](($state[$a] + $state[$b] + $mx) -band 0xFFFFFFFFL) $state[$d] = [BLAKE3]::RotR([uint32]($state[$d] -bxor $state[$a]), 16) $state[$c] = [uint32](($state[$c] + $state[$d]) -band 0xFFFFFFFFL) $state[$b] = [BLAKE3]::RotR([uint32]($state[$b] -bxor $state[$c]), 12) $state[$a] = [uint32](($state[$a] + $state[$b] + $my) -band 0xFFFFFFFFL) $state[$d] = [BLAKE3]::RotR([uint32]($state[$d] -bxor $state[$a]), 8) $state[$c] = [uint32](($state[$c] + $state[$d]) -band 0xFFFFFFFFL) $state[$b] = [BLAKE3]::RotR([uint32]($state[$b] -bxor $state[$c]), 7) } static hidden [void] Round([uint32[]]$state, [uint32[]]$m) { [BLAKE3]::G($state, 0, 4, 8, 12, $m[0], $m[1]) [BLAKE3]::G($state, 1, 5, 9, 13, $m[2], $m[3]) [BLAKE3]::G($state, 2, 6, 10, 14, $m[4], $m[5]) [BLAKE3]::G($state, 3, 7, 11, 15, $m[6], $m[7]) [BLAKE3]::G($state, 0, 5, 10, 15, $m[8], $m[9]) [BLAKE3]::G($state, 1, 6, 11, 12, $m[10], $m[11]) [BLAKE3]::G($state, 2, 7, 8, 13, $m[12], $m[13]) [BLAKE3]::G($state, 3, 4, 9, 14, $m[14], $m[15]) } static hidden [uint32[]] Compress([uint32[]]$cv, [uint32[]]$blockWords, [uint32]$counter, [uint32]$blockLen, [uint32]$flags) { $state = [uint32[]]::new(16) for ($i = 0; $i -lt 8; $i++) { $state[$i] = $cv[$i] } for ($i = 0; $i -lt 4; $i++) { $state[$i + 8] = [BLAKE3]::IV[$i] } $state[12] = $counter $state[13] = 0 $state[14] = $blockLen $state[15] = $flags $m = [uint32[]]::new(16) for ($i = 0; $i -lt 16; $i++) { $m[$i] = $blockWords[$i] } [BLAKE3]::Round($state, $m) for ($r = 1; $r -lt 7; $r++) { $nextM = [uint32[]]::new(16) for ($i = 0; $i -lt 16; $i++) { $nextM[$i] = $m[[BLAKE3]::MSG_PERMUTATION[$i]] } $m = $nextM [BLAKE3]::Round($state, $m) } $out = [uint32[]]::new(16) for ($i = 0; $i -lt 8; $i++) { $out[$i] = [uint32]($state[$i] -bxor $state[$i + 8] -bxor $cv[$i]) $out[$i + 8] = [uint32]($state[$i + 8] -bxor $cv[$i]) } return $out } # Build root chaining-value word array from Data (single-chunk, <=64 bytes) static hidden [uint32[]] BuildRootWords([uint32[]]$keyIV, [byte[]]$Data) { $CHUNK_START = [uint32]1; $CHUNK_END = [uint32]2 $blockWords = [uint32[]]::new(16) $dataLen = if ($null -ne $Data) { $Data.Length } else { 0 } if ($dataLen -gt 0) { $padded = [byte[]]::new(64) $copyLen = [System.Math]::Min($dataLen, 64) [Array]::Copy($Data, 0, $padded, 0, $copyLen) for ($i = 0; $i -lt 16; $i++) { $blockWords[$i] = [System.BitConverter]::ToUInt32($padded, $i * 4) } } $chunkCv = [BLAKE3]::Compress($keyIV, $blockWords, [uint32]0, [uint32]([System.Math]::Min($dataLen, 64)), $CHUNK_START -bor $CHUNK_END) $outWords = [uint32[]]::new(16) for ($i = 0; $i -lt 8; $i++) { $outWords[$i] = $chunkCv[$i] } return $outWords } # XOF output: counter-based expand from root words, produces OutputLength bytes static hidden [byte[]] XofOutput([uint32[]]$outWords, [uint32[]]$keyIV, [int]$OutputLength) { $ROOT = [uint32]8 $result = [byte[]]::new($OutputLength) $offset = 0 $counter = [uint32]0 while ($offset -lt $OutputLength) { $block = [BLAKE3]::Compress($keyIV, $outWords, $counter, [uint32]32, $ROOT) for ($i = 0; $i -lt 16 -and ($offset + ($i * 4)) -lt $OutputLength; $i++) { $b = [System.BitConverter]::GetBytes($block[$i]) $len = [System.Math]::Min(4, $OutputLength - $offset - ($i * 4)) [Array]::Copy($b, 0, $result, $offset + ($i * 4), $len) } $offset += 64 $counter++ } return $result } # Convert 32-byte key to 8 uint32 IV words (BLAKE3 keyed-hash mode) static hidden [uint32[]] KeyToIV([byte[]]$Key) { if ($null -eq $Key -or $Key.Length -ne 32) { throw [System.ArgumentException]::new("Key must be exactly 32 bytes for BLAKE3 keyed mode") } $kiv = [uint32[]]::new(8) for ($i = 0; $i -lt 8; $i++) { $kiv[$i] = [System.BitConverter]::ToUInt32($Key, $i * 4) } return $kiv } # Standard hash, 32-byte output static [byte[]] ComputeHash([byte[]]$Data) { return [BLAKE3]::ComputeHash($Data, 32) } # Standard hash, variable XOF output static [byte[]] ComputeHash([byte[]]$Data, [int]$OutputLength) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } if ($OutputLength -le 0) { throw [System.ArgumentOutOfRangeException]::new("OutputLength", "OutputLength must be > 0") } if ($OutputLength -gt 134217728) { throw [System.ArgumentOutOfRangeException]::new("OutputLength", "OutputLength must be <= 134217728") } $outWords = [BLAKE3]::BuildRootWords([BLAKE3]::IV, $Data) return [BLAKE3]::XofOutput($outWords, [BLAKE3]::IV, $OutputLength) } # Keyed hash: Data hashed with 32-byte Key, variable XOF output static [byte[]] ComputeHash([byte[]]$Data, [byte[]]$Key, [int]$OutputLength) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } if ($OutputLength -le 0) { throw [System.ArgumentOutOfRangeException]::new("OutputLength", "OutputLength must be > 0") } $keyIV = [BLAKE3]::KeyToIV($Key) $outWords = [BLAKE3]::BuildRootWords($keyIV, $Data) return [BLAKE3]::XofOutput($outWords, $keyIV, $OutputLength) } # Keyed hash with context info: derives sub-key from Key+Info via HMAC-SHA256, then keyed-hashes Data static [byte[]] ComputeHash([byte[]]$Data, [byte[]]$Key, [byte[]]$Info, [int]$OutputLength) { if ($null -eq $Data) { throw [System.ArgumentNullException]::new("Data") } if ($null -eq $Key) { throw [System.ArgumentNullException]::new("Key") } if ($OutputLength -le 0) { throw [System.ArgumentOutOfRangeException]::new("OutputLength", "OutputLength must be > 0") } $hmac = [System.Security.Cryptography.HMACSHA256]::new($Key) $infoBytes = if ($null -ne $Info -and $Info.Length -gt 0) { $Info } else { [byte[]]::new(0) } $derivedKey = $hmac.ComputeHash($infoBytes) $hmac.Dispose() $keyIV = [BLAKE3]::KeyToIV($derivedKey) $outWords = [BLAKE3]::BuildRootWords($keyIV, $Data) return [BLAKE3]::XofOutput($outWords, $keyIV, $OutputLength) } } |