public/Protect-Blob.ps1



function Protect-Blob() {
    Param(
        [Parameter(Position = 0, ValueFromPipeline = $true )]
        [Byte[]] $Blob,

        [Parameter(ParameterSetName = "PrivateKey")]
        [Byte[]] $PrivateKey,

        [Parameter(ParameterSetName = "Password")]
        [SecureString] $Password,

        [Parameter(ParameterSetName = "Keys")]
        [Byte[]] $Key,

        [Parameter(ParameterSetName = "Keys")]
        [Byte[]] $AuthenticationKey,

        [Byte[]] $InsecureInfo
    )

    $aes = [System.Security.Cryptography.Aes]::Create()
    $options = Get-ProtectOptions
    $aes.KeySize = $options.KeySize
    $aes.BlockSize = $options.BlockSize
    $aes.Mode = "CBC"
    $aes.Padding = "PKCS7"

    $Headers = $InsecureInfo
    if(!$Headers) {
        $Headers = New-Object 'Byte[]' -ArgumentList 0
    }
    $headerSize = ($options.SaltSize / 8) * 2
    $headerSize = $headerSize + $Headers.Length
    $header = New-Object 'byte[]' -ArgumentList $headerSize
    [Array]::Copy($headers, $header, $headers.Length)
    $headerIndex = $Headers.Length

    $aes.GenerateIV();
    $iv = $aes.IV
    if($Password -or $PrivateKey) {
        if($Password) {
            $PrivateKey = $Password  | ConvertTo-UnprotectedBytes 
        }
        
        $salt = New-ProtectBlobSalt -SaltSize $options.SaltSize
       
        $generator = New-Object System.Security.Cryptography.Rfc2898DeriveBytes `
            -ArgumentList $PrivateKey, $salt, ($options.Iterations)
        $salt = $generator.Salt
      
        $Key = $generator.GetBytes($options.KeySize / 8)

        [Array]::Copy($salt, 0, $header, $headerIndex, $salt.Length)
        $headerIndex += $salt.Length
        $generator.Dispose()

        $salt = New-ProtectBlobSalt -SaltSize $options.SaltSize
        $generator = New-Object System.Security.Cryptography.Rfc2898DeriveBytes `
            -ArgumentList $PrivateKey, $salt, ($options.Iterations)
        $salt = $generator.Salt

        $AuthenticationKey = $generator.GetBytes($options.KeySize / 8)

        [Array]::Copy($salt, 0, $header, $headerIndex, $salt.Length)
        $headerIndex += $salt.Length
        $generator.Dispose()

        [Array]::Clear($pwd, 0, $pwd.Length)
    }

    if(!$Key -or $Key.Length -eq 0) {
        Throw [ArgumentException] "Key must not be null or empty"
    }

    if(!$AuthenticationKey -or $AuthenticationKey.Length -eq 0) {
        Throw [ArgumentException] "AuthenticationKey must not be null or empty"
    }

    $encryptor = $aes.CreateEncryptor($key, $iv)
    $ms = New-Object System.IO.MemoryStream
    $cryptoStream = New-Object System.Security.Cryptography.CryptoStream($ms, $encryptor, "Write")
    $binaryWriter = New-Object System.IO.BinaryWriter($cryptoStream)

    $binaryWriter.Write($Blob)
    $binaryWriter.Flush()
    $cryptoStream.Flush()
    $cryptoStream.FlushFinalBlock()
    $ms.Flush()
    $encryptedBlob = $ms.ToArray()

    $binaryWriter.Dispose()
    $cryptoStream.Dispose()
    $ms.Dispose()

    $hmac = New-Object System.Security.Cryptography.HMACSHA256
    $hmac.Key = $AuthenticationKey
    $ms = New-Object System.IO.MemoryStream
    $binaryWriter = New-Object System.IO.BinaryWriter($ms)

    try {
    
        $binaryWriter.Write($Header);
        $binaryWriter.Write($iv);

        $binaryWriter.Write($encryptedBlob);
        $binaryWriter.Flush()
        $ms.Flush()
        $data = $ms.ToArray()
        $hash = $hmac.ComputeHash($data)
        $binaryWriter.Write($hash);

        $binaryWriter.Flush()
        $ms.Flush()

        return $ms.ToArray()
    } finally {
        $binaryWriter.Dispose()
        $ms.Dispose()
        $hmac.Dispose()
    }
}