HarinezumiSama.Utilities.Cryptography.ps1
<#PSScriptInfo
.VERSION 0.1.0 .GUID fdc9a67a-4e68-4893-859f-a66738e5a85f .AUTHOR Vitalii Maklai a.k.a. HarinezumiSama .COMPANYNAME .COPYRIGHT Copyright (C) Vitalii Maklai .TAGS Cryptography Encryption .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .DESCRIPTION Provides: - class HarinezumiSamaUtilitiesCryptographyHelper - cmdlet Encrypt-Stream - cmdlet Decrypt-Stream - cmdlet Encrypt-File - cmdlet Decrypt-File #> #Requires -Version 5 using namespace System using namespace System.Collections using namespace System.IO using namespace System.Security.Cryptography using namespace System.Security.Cryptography.X509Certificates using namespace System.Text using namespace Microsoft.Win32 $Script:ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Microsoft.PowerShell.Core\Set-StrictMode -Version 1 class HarinezumiSamaUtilitiesCryptographyHelper { hidden static [int] $_KeySize = [HarinezumiSamaUtilitiesCryptographyHelper]::_GetKeySize() hidden static [int] $_BlockSize = [HarinezumiSamaUtilitiesCryptographyHelper]::_GetBlockSize() hidden static [int] $_SaltSize = 32 hidden static [int] $_IterationCount = 65536 hidden HarinezumiSamaUtilitiesCryptographyHelper() { # Nothing to do } static [psobject] GetEncryptionData([string] $password, [byte[]] $salt) { [Rfc2898DeriveBytes] $deriveBytes = [Rfc2898DeriveBytes]::new( $password, [HarinezumiSamaUtilitiesCryptographyHelper]::_SaltSize, [HarinezumiSamaUtilitiesCryptographyHelper]::_IterationCount) try { if (![object]::ReferenceEquals($salt, $null)) { $deriveBytes.Salt = $salt } [byte[]] $key = $deriveBytes.GetBytes([HarinezumiSamaUtilitiesCryptographyHelper]::_KeySize) [byte[]] $iv = $deriveBytes.GetBytes([HarinezumiSamaUtilitiesCryptographyHelper]::_BlockSize) [psobject] $result = [psobject]::new() ` | Add-Member -MemberType NoteProperty -Name Key -Value $key -PassThru ` | Add-Member -MemberType NoteProperty -Name IV -Value $iv -PassThru ` | Add-Member -MemberType NoteProperty -Name Salt -Value $deriveBytes.Salt -PassThru return $result } finally { $deriveBytes.Dispose() } } static [psobject] GetEncryptionData([string] $password) { return [HarinezumiSamaUtilitiesCryptographyHelper]::GetEncryptionData($password, $null) } static [SymmetricAlgorithm] CreateAlgorithm() { [SymmetricAlgorithm] $algorithm = [AesManaged]::new() try { $algorithm.Mode = [CipherMode]::CBC $algorithm.Padding = [PaddingMode]::PKCS7 } catch { $algorithm.Dispose() } return $algorithm } static [int] GetSaltSize() { return [HarinezumiSamaUtilitiesCryptographyHelper]::_SaltSize } hidden static [int] _GetKeySize() { [SymmetricAlgorithm] $algorithm = [HarinezumiSamaUtilitiesCryptographyHelper]::CreateAlgorithm() try { return $algorithm.KeySize / 8 } finally { $algorithm.Dispose() } } hidden static [int] _GetBlockSize() { [SymmetricAlgorithm] $algorithm = [HarinezumiSamaUtilitiesCryptographyHelper]::CreateAlgorithm() try { return $algorithm.BlockSize / 8 } finally { $algorithm.Dispose() } } } function Encrypt-Stream { [CmdletBinding(PositionalBinding = $false)] param ( [Parameter(ValueFromPipeline = $true)] [Stream] $InputStream, [Parameter()] [string] $Password, [Parameter()] [Stream] $OutputStream ) process { if ($InputStream -eq $null) { throw [ArgumentNullException]::new('InputStream') } if (!$InputStream.CanRead) { throw [ArgumentException]::new('The input stream (plain data) must be readable.', 'InputStream') } if ($OutputStream -eq $null) { throw [ArgumentNullException]::new('OutputStream') } if (!$OutputStream.CanWrite) { throw [ArgumentException]::new('The output stream (cipher data) must be writable.', 'OutputStream') } [psobject] $encryptionData = [HarinezumiSamaUtilitiesCryptographyHelper]::GetEncryptionData($Password) [SymmetricAlgorithm] $algorithm = [HarinezumiSamaUtilitiesCryptographyHelper]::CreateAlgorithm() try { $algorithm.IV = $encryptionData.IV $algorithm.Key = $encryptionData.Key $OutputStream.Write($encryptionData.Salt, 0, $encryptionData.Salt.Length) | Out-Null [ICryptoTransform] $encryptor = $algorithm.CreateEncryptor() try { [CryptoStream] $cryptoStream = [CryptoStream]::new($InputStream, $encryptor, [CryptoStreamMode]::Read) try { $cryptoStream.CopyTo($OutputStream) | Out-Null } finally { $cryptoStream.Dispose() } } finally { $encryptor.Dispose() } } finally { $algorithm.Dispose() } } } function Decrypt-Stream { [CmdletBinding(PositionalBinding = $false)] param ( [Parameter(ValueFromPipeline = $true)] [Stream] $InputStream, [Parameter()] [string] $Password, [Parameter()] [Stream] $OutputStream ) process { if ($InputStream -eq $null) { throw [ArgumentNullException]::new('InputStream') } if (!$InputStream.CanRead) { throw [ArgumentException]::new('The input stream (cipher data) must be readable.', 'InputStream') } if ($OutputStream -eq $null) { throw [ArgumentNullException]::new('OutputStream') } if (!$OutputStream.CanWrite) { throw [ArgumentException]::new('The output stream (plain data) must be writable.', 'OutputStream') } [byte[]] $salt = [byte[]]::new([HarinezumiSamaUtilitiesCryptographyHelper]::GetSaltSize()) [int] $saltBytesRead = $InputStream.Read($salt, 0, $salt.Length) if ($saltBytesRead -ne $salt.Length) { throw 'The cipher data is corrupted.' } [psobject] $encryptionData = [HarinezumiSamaUtilitiesCryptographyHelper]::GetEncryptionData($Password, $salt) [SymmetricAlgorithm] $algorithm = [HarinezumiSamaUtilitiesCryptographyHelper]::CreateAlgorithm() try { $algorithm.IV = $encryptionData.IV $algorithm.Key = $encryptionData.Key [ICryptoTransform] $decryptor = $algorithm.CreateDecryptor() try { [CryptoStream] $cryptoStream = [CryptoStream]::new($InputStream, $decryptor, [CryptoStreamMode]::Read) try { $cryptoStream.CopyTo($OutputStream) | Out-Null } finally { $cryptoStream.Dispose() } } finally { $decryptor.Dispose() } } finally { $algorithm.Dispose() } } } function Encrypt-File { [CmdletBinding(PositionalBinding = $false)] param ( [Parameter(ValueFromPipeline = $true)] [string] $Path, [Parameter()] [string] $Password, [Parameter()] [string] $DestinationPath ) process { if ([string]::IsNullOrWhiteSpace($Path)) { throw [ArgumentException]::new('The file path cannot be blank.', 'Path') } if ([string]::IsNullOrWhiteSpace($DestinationPath)) { throw [ArgumentException]::new('The destination file path cannot be blank.', 'DestinationPath') } [FileStream] $inputStream = [File]::Open($Path, [FileMode]::Open, [FileAccess]::Read, [FileShare]::Read) try { [FileStream] $outputStream = [File]::Open($DestinationPath, [FileMode]::Create, [FileAccess]::Write, [FileShare]::Read) try { Encrypt-Stream -InputStream $inputStream -Password $Password -OutputStream $outputStream } finally { $outputStream.Dispose() } } finally { $inputStream.Dispose() } } } function Decrypt-File { [CmdletBinding(PositionalBinding = $false)] param ( [Parameter(ValueFromPipeline = $true)] [string] $Path, [Parameter()] [string] $Password, [Parameter()] [string] $DestinationPath ) process { if ([string]::IsNullOrWhiteSpace($Path)) { throw [ArgumentException]::new('The file path cannot be blank.', 'Path') } if ([string]::IsNullOrWhiteSpace($DestinationPath)) { throw [ArgumentException]::new('The destination file path cannot be blank.', 'DestinationPath') } [FileStream] $inputStream = [File]::Open($Path, [FileMode]::Open, [FileAccess]::Read, [FileShare]::Read) try { [FileStream] $outputStream = [File]::Open($DestinationPath, [FileMode]::Create, [FileAccess]::Write, [FileShare]::Read) try { Decrypt-Stream -InputStream $inputStream -Password $Password -OutputStream $outputStream } finally { $outputStream.Dispose() } } finally { $inputStream.Dispose() } } } |