Paschwords.ps1

param ([string]$database, [string]$keyfile, [switch]$noclip, [switch]$notime)

# Returns the derived key for HMAC.
function derivekeyfrompassword ([object]$Password, [byte[]]$Salt) {if ($Password -is [string]) {$secure = ConvertTo-SecureString $Password -AsPlainText -Force}
elseif ($Password -is [SecureString]) {$secure = $Password}
else {throw "The Password must be a string or SecureString."}

$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
try {$plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)}
finally {[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)}

$pbkdf2 = [Security.Cryptography.Rfc2898DeriveBytes]::new($plain, $Salt, 100000, [Security.Cryptography.HashAlgorithmName]::SHA256)

try {return $pbkdf2.GetBytes(64)}
finally {$pbkdf2.Dispose()}}

# Verify HMAC, decrypt, and return plaintext bytes.
function unprotectbytesaeshmac ([byte[]]$Encrypted, [byte[]]$Key) {

# Split byte object by bytes in order to maintain array.
function slicebytes([byte[]]$bytes, [int]$start, [int]$length) {$result = New-Object byte[] $length; [Array]::Copy($bytes, $start, $result, 0, $length); return $result}

# Ensure HMAC integrity remained intact.
function comparehmac($a, $b) {if ($a.Length -ne $b.Length) {return $false}; $result = 0; 
for ($i = 0; $i -lt $a.Length; $i++) {$result = $result -bor ($a[$i] -bxor $b[$i])}
return ($result -eq 0)}

$aesKey = slicebytes $Key 0 32; $hmacKey = slicebytes $Key 32 32; $hmacStored = slicebytes $Encrypted 0 32; $iv = slicebytes $Encrypted 32 16; $cipher = slicebytes $Encrypted 48 ($Encrypted.Length - 48); $hmacData = $iv + $cipher; $hmac = [System.Security.Cryptography.HMACSHA256]::new($hmacKey); $hmacComputed = $hmac.ComputeHash($hmacData)
if (-not (comparehmac $hmacStored $hmacComputed)) {Write-Host -f red "`t ❌ Encrypted module failed HMAC verification. Possible tampering detected. Aborting.`n"; return}
$aes = [System.Security.Cryptography.Aes]::Create(); $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC; $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7; $aes.Key = $aesKey; $aes.IV = $iv; $decryptor = $aes.CreateDecryptor()
try {return $decryptor.TransformFinalBlock($cipher, 0, $cipher.Length)}
finally {$hmac.Dispose(); $decryptor.Dispose(); $aes.Dispose()}}

# Initialize environment.
sl $powershell\Modules\Paschwords; $encfile = Resolve-Path 'paschwords.enc'; [byte[]]$raw = [IO.File]::ReadAllBytes($encfile); $salt = $raw[0..15]; $blob = $raw[16..($raw.Length - 1)]

# User interaction.
Write-Host -f green "`n`t Enter password to decrypt the module " -n; $password = Read-Host -AsSecureString
if ([string]::IsNullOrEmpty($password) -or $password.length -lt 1) {Write-Host -f red "`t No password provided. Aborting.`n"; return}

# Get key, salt and verify HMAC.
$key = derivekeyfrompassword -Password $password -Salt $salt; $plaintextBytes = unprotectbytesaeshmac -Encrypted $blob -Key $key; 

if (-not [string]::IsNullOrEmpty($plaintextBytes) -and $plaintextBytes.length -gt 0) {# Unzip.
$ms = [System.IO.MemoryStream]::new($plaintextBytes); $gzip = [System.IO.Compression.GzipStream]::new($ms, [System.IO.Compression.CompressionMode]::Decompress); $out = [System.IO.MemoryStream]::new(); $buffer = New-Object byte[] 4096
while (($read = $gzip.Read($buffer, 0, $buffer.Length)) -gt 0) {$out.Write($buffer, 0, $read)}
$gzip.Close(); [byte[]]$decompressed = $out.ToArray(); $code = [System.Text.Encoding]::UTF8.GetString($decompressed)

# Build command line and run Paschwords.

$cmd = "paschwords -database `"$database`" -keyfile `"$keyfile`""
if ($noclip) {$cmd += " -noclip"}
if ($notime) {$cmd += " -notime"}
& {Invoke-Expression $code; Invoke-Expression $cmd}; $password = $null; $key = $null; ""}