Private/AesCfb.psm1

#!/usr/bin/env pwsh
using namespace System
using namespace System.Security.Cryptography

using module ./Utilities.psm1

class AesCfb : CryptobaseUtils {
  static [int] $BlockSize = 16

  static [byte[]] Encrypt([byte[]]$plainbytes, [byte[]]$key, [byte[]]$iv) {
  if ($null -eq $plainbytes) { throw [ArgumentNullException]::new("plaintext") }
  if ($null -eq $key) { throw [ArgumentNullException]::new("key") }
  if ($null -eq $iv) { throw [ArgumentNullException]::new("iv") }
    if ($iv.Length -ne [AesCfb]::BlockSize) { throw [ArgumentException]::new("IV must be 16 bytes.") }

    $aes = [Aes]::Create()
    $aes.Key = $key
    $aes.Mode = [CipherMode]::ECB
    $aes.Padding = [PaddingMode]::None

    $result = [AesCfb]::ProcessCore($plainbytes, $aes, $iv, $true)
    $aes.Dispose()
    return $result
  }

  static [byte[]] Decrypt([byte[]]$ciphertext, [byte[]]$key, [byte[]]$iv) {
    if ($null -eq $ciphertext) { throw [ArgumentNullException]::new("ciphertext") }
    if ($null -eq $key) { throw [ArgumentNullException]::new("key") }
    if ($null -eq $iv) { throw [ArgumentNullException]::new("iv") }
    if ($iv.Length -ne [AesCfb]::BlockSize) { throw [ArgumentException]::new("IV must be 16 bytes.") }

    $aes = [Aes]::Create()
    $aes.Key = $key
    $aes.Mode = [CipherMode]::ECB
    $aes.Padding = [PaddingMode]::None

    $result = [AesCfb]::ProcessCore($ciphertext, $aes, $iv, $false)
    $aes.Dispose()
    return $result
  }

  hidden static [byte[]] ProcessCore([byte[]]$inData, [Aes]$aes, [byte[]]$iv, [bool]$encrypting) {
    $output = [byte[]]::new($inData.Length)
    $fr = [byte[]]::new([AesCfb]::BlockSize) # Feedback register
    $fre = [byte[]]::new([AesCfb]::BlockSize) # Encrypted feedback register

    [Array]::Copy($iv, $fr, [AesCfb]::BlockSize)
    $encryptor = $aes.CreateEncryptor()

    $pos = 0
    while ($pos -lt $inData.Length) {
      [void]$encryptor.TransformBlock($fr, 0, [AesCfb]::BlockSize, $fre, 0)

      $bytesToProcess = [Math]::Min([AesCfb]::BlockSize, $inData.Length - $pos)
      for ($i = 0; $i -lt $bytesToProcess; $i++) {
        $output[$pos + $i] = [byte]($inData[$pos + $i] -bxor $fre[$i])
      }

      if ($encrypting) {
        [Array]::Copy($output, $pos, $fr, 0, $bytesToProcess)
      } else {
        [Array]::Copy($inData, $pos, $fr, 0, $bytesToProcess)
      }

      if ($bytesToProcess -lt [AesCfb]::BlockSize) {
        [Array]::Clear($fr, $bytesToProcess, [AesCfb]::BlockSize - $bytesToProcess)
      }

      $pos += $bytesToProcess
    }

    $encryptor.Dispose()
    return $output
  }
}