0
votes

I wrote some code to encrypt and decrypt files with PowerShell, using AES symmetric encryption. The functions depend on the built-in AesManaged class in the .NET Framework.

When I run this script, I am getting different file hashes for the original source file and the encrypted + decrypted version of the same file. Something is changing the file during the encryption or decryption process, but I am not sure what is causing the change. Every time I run the script, the file hash of the decrypted version of the source file changes. It isn't providing me a consistent result.

Does anyone see why this code would be messing with the file contents during encryption or decryption?

function Encrypt-File {
    [CmdletBinding()]
    param (
      [Parameter(Mandatory = $false)]
      [byte[]] $Key,
      # Path to the file that will be encrypted
      [Parameter(Mandatory = $true)]
      [string] $Path,
      # Path to the resulting encrypted file
      [string] $Destination
    )
    if (-not $PSBoundParameters['Destination']) {
      $Destination = (Resolve-Path -Path $Path).Path + '.encrypted'
    }

    $AES = [System.Security.Cryptography.AesManaged]::new()
    if ($PSBoundParameters['Key']) { $AES.Key = $Key }

    $Encryptor = $AES.CreateEncryptor()

    Write-Verbose -Message 'Reading source file'

    $FileStream = [System.IO.FileStream]::new($Path, [System.IO.FileMode]::OpenOrCreate)
    $FileWriter = [System.IO.File]::OpenWrite($Destination)

    $CryptoStream = [System.Security.Cryptography.CryptoStream]::new($FileWriter, $Encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
    Write-Verbose -Message 'Created CryptoStream'

    $FileStream.CopyTo($CryptoStream)

    Write-Verbose -Message 'Finished writing bytes to CryptoStream'

    $AES.Key | Set-Content -Path ($Path + '.encrypted.key') -AsByteStream
    $CryptoStream.Flush()
    $CryptoStream.FlushFinalBlock()
    $FileWriter.Flush()
    $CryptoStream.Clear()
    $FileWriter.Close()
    $FileStream.Close()
}

function Decrypt-File {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $false)]
    [string] $KeyPath,
    # Path to the file that will be decrypted
    [Parameter(Mandatory = $true)]
    [string] $Path,
    # Path to the decrypted file
    [string] $Destination
  )
  if (!$PSBoundParameters['KeyPath']) { $KeyPath = $Path + '.key' }
  if (!$PSBoundParameters['Destination']) { $Destination = $Path + '.decrypted' }

  $AES = [System.Security.Cryptography.AesManaged]::new()
  $AES.Key = Get-Content -AsByteStream -Path $KeyPath

  $Decryptor = $AES.CreateDecryptor()

  $EncryptedFile = [System.IO.File]::OpenRead($Path)
  $DecryptedFile = [System.IO.File]::OpenWrite($Destination)

  $CryptoStream = [System.Security.Cryptography.CryptoStream]::new($DecryptedFile, $Decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)

  $EncryptedFile.CopyTo($CryptoStream)

  $CryptoStream.Flush()
  $DecryptedFile.Flush()
  $EncryptedFile.Flush()
  $CryptoStream.Clear()
  $EncryptedFile.Close()
  $DecryptedFile.Close()
}

$SourceFile = "$HOME/deadcat.m4a"
$EncryptedFile = "$HOME/deadcat.m4a.encrypted"
$DecryptedFile = "$HOME/deadcat.m4a.decrypted"

# Encrypt and then decrypt the file
Encrypt-File -Path $SourceFile -Verbose
Decrypt-File -Path $EncryptedFile -Verbose

# Original source file and decrypted file should both match
Get-FileHash -Path $HOME/deadcat.m4a
Get-FileHash -Path $DecryptedFile
1
What about the contents, do they make sense after decryption? Because avalance effect is highly desired in hashing, a tiny change, maybe a byte order mark or such, could cause this. - vonPryz
@vonPryz Good question. I looked at the first five or ten bytes of the files, and they differed quite a bit. I would prefer to find a way to get equal inputs / outputs byte-for-byte, using this encryption mechanism, so that I can validate the file hashes. It'd be great if this functionality eventually made its way into the core PowerShell releases. - user189198

1 Answers

0
votes

The promlem is in this:

The IV property is automatically set to a new random value whenever you create a new instance of one of the SymmetricAlgorithm classes

So you should initialize IV value wtih the same bytes every time. Example: AES. Encrypt array of bytes in powershell