File Decrypting Script

The following PowerShell script can be used to decrypt files encrypted by WinSCP, when you do not have WinSCP available.

For C# version of the AesCtrTransform function, see Can I use AES in CTR mode in .NET?

param (
    [Parameter(Mandatory = $True)]
    $path,
    [Parameter(Mandatory = $True)]
    $key
)
 
function AesCtrTransform ($key, $salt, $inputStream, $outputStream)
{
    $aes = New-Object System.Security.Cryptography.AesManaged -Property @{
        Mode = [System.Security.Cryptography.CipherMode]::ECB
        Padding = [System.Security.Cryptography.PaddingMode]::None
    }
 
    $blockSize = $aes.BlockSize / 8
 
    $counter = $salt.Clone()
 
    $xorMask = New-Object System.Collections.Queue<byte>
 
    $zeroIv = New-Object byte[] $blockSize
    $counterEncryptor = $aes.CreateEncryptor($key, $zeroIv)
 
    while (($b = $inputStream.ReadByte()) -ne -1)
    {
        if ($xorMask.Count -eq 0)
        {
            $counterModeBlock = New-Object byte[] $blockSize
 
            $counterEncryptor.TransformBlock(
                $counter, 0, $counter.Length, $counterModeBlock, 0) | Out-Null
 
            for ($i2 = $counter.Length - 1; $i2 -ge 0; $i2--)
            {
                if ($counter[$i2] -eq 0xFF)
                {
                    $counter[$i2] = 0
                }
                else
                {
                    ++$counter[$i2]
                    break
                }
            }
 
            foreach ($b2 in $counterModeBlock)
            {
                $xorMask.Enqueue($b2)
            }
        }
 
        $mask = $xorMask.Dequeue()
        $outputStream.WriteByte([byte](([byte]$b) -bxor $mask))
    }
}
 
try
{
    $aes = New-Object System.Security.Cryptography.AesManaged
 
    $key = [System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::Parse($key).Value
    $keySize = $aes.KeySize / 8
    if ($key.Length -ne $keySize)
    {
        throw "Invalid key size (need $keySize bytes)"
    }
    
    $name = Split-Path -Leaf $path
 
    $aesCtrExt = ".aesctr.enc"
    if (-not ($name -like "*$aesCtrExt"))
    {
        throw "$name has not a known encrypted file extension [$aesCtrExt]"
    }
 
    $base64 = $name.SubString(0, $name.Length - $aesCtrExt.Length).Replace("_", "/")
    $padding = 4 - ($base64.Length % 4)
    if (($padding -gt 0) -and ($padding -lt 4))
    {
        $base64 = "=" * $padding
    }
 
    $buffer = [System.Convert]::FromBase64String($base64)
    $blockSize = $aes.BlockSize / 8
    $nameSalt = $buffer[0..($blockSize - 1)]
 
    $nameInputStream =
        New-Object System.IO.MemoryStream(
            $buffer, $nameSalt.Count, ($buffer.Length - $nameSalt.Length))
 
    $nameOutputStream = New-Object System.IO.MemoryStream
 
    AesCtrTransform $key $nameSalt $nameInputStream $nameOutputStream
 
    $name = [System.Text.Encoding]::UTF8.GetString($nameOutputStream.ToArray())
 
    Write-Host "Decrypted filename: $name"
    $parentPath = (Split-Path -Parent $path)
    if ($parentPath)
    {
        $outPath = Join-Path $parentPath $name
    }
    else
    {
        $outPath = $name
    }
 
    if (Test-Path $outPath)
    {
        throw "$outPath already exists"
    }
 
    if ((Get-Item $path).Length -eq 0)
    {
        Write-Host "Creating empty file..."
        New-Item $outPath -Type file | Out-Null
    }
    else
    {
        $fileInputStream = [System.IO.File]::OpenRead($path)
 
        $aesMagic = [System.Text.Encoding]::UTF8.GetBytes("aesctr..........")
 
        $magic = New-Object byte[] $aesMagic.Length
        $fileSalt = New-Object byte[] $blockSize
 
        if (($fileInputStream.Read($magic, 0, $magic.Length) -lt $magic.Length) -or
            (-not [System.Linq.Enumerable]::SequenceEqual($magic, $aesMagic)) -or
            ($fileInputStream.Read($fileSalt, 0, $fileSalt.Length) -lt $fileSalt.Length))
        {
            throw "$path does not have valid header"
        }
 
        $fileOutputStream = [System.IO.File]::Create($outPath)
 
        Write-Host "Decrypting contents..."
 
        AesCtrTransform $key $fileSalt $fileInputStream $fileOutputStream
 
        $fileOutputStream.Dispose()
    }
 
    [System.IO.File]::SetLastWriteTime($outPath, [System.IO.File]::GetLastWriteTime($path))
 
    Write-Host "Done."
 
    exit 0
}
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    exit 1
}

Last modified: by martin