'DPAPI ProtectedMemory.Protect is not encrypting byte array
I was beginning to explore the DPAPI and my very first sample code does not work. What I expected was for my byte array to change after a call to [ProtectedMemory]::Protect(). However, the byte array was exactly the same before and after the call. So either there is something I don't understand about Powershell (likely) OR there is something I don't understand about using the DPAPI (likely), OR the DPAPI is a scam that does not actually encrypt anything (unlikely). Here is my sample code:
using namespace System.Security.Cryptography
using namespace System.Text
function Protect-String {
param (
[Parameter(ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$Secret
)
begin {
Add-Type -AssemblyName System.Security
}
process {
$bytes = [UnicodeEncoding]::UTF8.GetBytes($secret)
$padding = [byte[]]::new(16 - $bytes.length % 16)
$bytes += $padding
Write-host "Original: $bytes"
$Scope = [MemoryProtectionScope]::SameLogon
[ProtectedMemory]::Protect($bytes, $Scope)
Write-host "After : $bytes"
}
}
output:
PS C:\> Protect-String something
Original: 115 111 109 101 116 104 105 110 103 0 0 0 0 0 0 0
After : 115 111 109 101 116 104 105 110 103 0 0 0 0 0 0 0
What I know so far:
This code is for Powershell 5 under .Net and will not work with Powershell 7 under .Net Core as the System.Security.Cryptography namespace does not contain the DPAPI classes - these rely upon Windows OS features.
The Protect should encrypt the byte array in place. Since my input and output are the same, I wonder if PowerShell is passing a copy/clone of the byte[]?
Solution 1:[1]
The solution (as given by @TheMadTechnician in the comments) was to specify the type for the $bytes array. Without that specificity, Powershell had to work with ambiguity and when asked to append an array to an array the result was an object[]. When it came time to pass the array to protect() the object[] array was coerced into a new byte[] array which was encrypted and then lost.
My final "play" code was this:
using namespace System.Security.Cryptography
using namespace System.Text
Add-Type -AssemblyName System.Security
# Requires Powershell for Windows
function Protect-MemoryString {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$Secret,
[Parameter()]
[ValidateNotNullOrEmpty()]
[MemoryProtectionScope]$Scope = [MemoryProtectionScope]::SameLogon
)
process {
[byte[]]$bytes = [UnicodeEncoding]::UTF8.GetBytes($secret)
$bytes += [byte[]]::new((16 - $bytes.Count % 16) % 16)
Write-Verbose "Original: $bytes"
[ProtectedMemory]::Protect($bytes, $Scope)
Write-Verbose "After : $bytes"
[System.Convert]::ToBase64String($bytes)
}
}
Note that the output is a Base64 encoded string - I did not convert back to a UTF8 string because after the encryption the data is no longer string data and certainly is not UTF8 encoded string data.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | nickdmax |
