File Decrypting Script
The following PowerShell script can be used to decrypt files encrypted by WinSCP, when you do not have WinSCP available.
The script works both in legacy Windows PowerShell and modern PowerShell [Core], though it runs slower in the latter.
For C# version of the AesCtrTransform
function, see Can I use AES in CTR mode in .NET?
Advertisement
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() # Particularly in pwsh, it runs much faster with non-generic Queue $xorMask = New-Object System.Collections.Queue $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 try { # .NET >= 5 $key = [System.Convert]::FromHexString($key) } catch [System.Management.Automation.RuntimeException] { # .NET Framework $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 }
Advertisement