AzPolicyLens.Wiki.Encryption.Helper.psm1

<#
================================================================================================================
AUTHOR: Tao Yang
DATE: 11/09/2025
NAME: AzPolicyLens.Wiki.Encryption.Helper.psm1
VERSION: 2.0.0
COMMENT: this nested module contains the encryption and security utility functions for AzPolicyLens.Wiki module
================================================================================================================
#>

using module ./AzPolicyLens.Wiki.Utility.Helper.psm1
#function to decrypt file using AES
function decryptStuff {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, ParameterSetName = 'FileInKeyFile')]
    [Parameter(Mandatory = $true, ParameterSetName = 'FileInKeyDirect')]
    [ValidateScript({ Test-Path $_ -PathType Leaf })]
    [string]$InputFilePath,

    [Parameter(Mandatory = $true, ParameterSetName = 'TextInKeyFile')]
    [Parameter(Mandatory = $true, ParameterSetName = 'TextInKeyDirect')]
    [ValidateNotNullOrEmpty()]
    [string]$InputText,

    [Parameter(Mandatory = $false)]
    [string]$OutputFilePath,

    [Parameter(Mandatory = $true, ParameterSetName = 'FileInKeyFile')]
    [Parameter(Mandatory = $true, ParameterSetName = 'TextInKeyFile')]
    [ValidateScript({ Test-Path $_ -PathType Leaf })]
    [string]$KeyFilePath,

    [Parameter(Mandatory = $true, ParameterSetName = 'FileInKeyDirect')]
    [Parameter(Mandatory = $true, ParameterSetName = 'TextInKeyDirect')]
    [string]$AESKey,

    [Parameter(Mandatory = $true, ParameterSetName = 'FileInKeyDirect')]
    [Parameter(Mandatory = $true, ParameterSetName = 'TextInKeyDirect')]
    [string]$AESIV
  )

  try {
    Write-Verbose "[$(getCurrentUTCString)]: Starting AES decryption" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $startTime = [datetime]::UtcNow

    # Load key and IV
    if ($PSCmdlet.ParameterSetName -eq 'FileInKeyFile' -or $PSCmdlet.ParameterSetName -eq 'TextInKeyFile') {
      $KeyFullPath = (Resolve-Path -Path $KeyFilePath).path
      Write-Verbose "[$(getCurrentUTCString)]: Loading key from file: $KeyFullPath" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $keyJson = [System.IO.File]::ReadAllText($KeyFullPath)
      $keyObject = $keyJson | ConvertFrom-Json
      $keyBytes = [Convert]::FromBase64String($keyObject.Key)
      $ivBytes = [Convert]::FromBase64String($keyObject.IV)
    } else {
      $keyBytes = [Convert]::FromBase64String($AESKey)
      $ivBytes = [Convert]::FromBase64String($AESIV)
    }
    if ($InputFilePath) {
      # Read encrypted file
      Write-Verbose "[$(getCurrentUTCString)]: Reading encrypted file: $InputFilePath" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $InputText = [System.IO.File]::ReadAllText($InputFilePath)
    }
    $encryptedObject = $InputText | ConvertFrom-Json

    # Extract encrypted data
    $encryptedBytes = [Convert]::FromBase64String($encryptedObject.EncryptedData)
    Write-Verbose "[$(getCurrentUTCString)]: Encrypted size: $($encryptedBytes.Length) bytes" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    Write-Verbose "[$(getCurrentUTCString)]: Algorithm: $($encryptedObject.Algorithm)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    Write-Verbose "[$(getCurrentUTCString)]: Compressed: $($encryptedObject.Compressed)" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

    # Create AES instance
    $aes = [System.Security.Cryptography.Aes]::Create()
    $aes.Key = $keyBytes
    $aes.IV = $ivBytes
    $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7

    # Create decryptor
    $decryptor = $aes.CreateDecryptor()

    # Decrypt data
    Write-Verbose "[$(getCurrentUTCString)]: Decrypting data" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $decryptedBytes = $decryptor.TransformFinalBlock($encryptedBytes, 0, $encryptedBytes.Length)

    # Decompress if needed
    if ($encryptedObject.Compressed) {
      Write-Verbose "[$(getCurrentUTCString)]: Decompressing data" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $memoryStream = [System.IO.MemoryStream]::new($decryptedBytes)
      $gzipStream = New-Object System.IO.Compression.GZipStream($memoryStream, [System.IO.Compression.CompressionMode]::Decompress)
      $outputStream = New-Object System.IO.MemoryStream
      $gzipStream.CopyTo($outputStream)
      $decryptedBytes = $outputStream.ToArray()

      # Cleanup compression streams
      $gzipStream.Dispose()
      $memoryStream.Dispose()
      $outputStream.Dispose()
    }

    Write-Verbose "[$(getCurrentUTCString)]: Decrypted size: $($decryptedBytes.Length) bytes" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

    # Output result
    if ($OutputFilePath) {
      [System.IO.File]::WriteAllBytes($OutputFilePath, $decryptedBytes)
      Write-Verbose "[$(getCurrentUTCString)]: Decrypted file saved to: $OutputFilePath" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $result = $OutputFilePath

    } else {
      $result = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
    }

    # Cleanup
    $decryptor.Dispose()
    $aes.Dispose()

    $endTime = [datetime]::UtcNow
    $timeTaken = New-TimeSpan -Start $startTime -End $endTime
    Write-Verbose "[$(getCurrentUTCString)]: AES decryption completed in $($timeTaken.TotalSeconds) seconds" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

    return $result
  } catch {
    Write-Error "[$(getCurrentUTCString)]: AES decryption failed: $_" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    throw
  } finally {
    if ($aes) { $aes.Dispose() }
    if ($decryptor) { $decryptor.Dispose() }
  }
}