Private/Unprotect-MachineBoundString.ps1
|
Function Unprotect-MachineBoundString { <# Reverses Protect-MachineBoundString. Takes the full 'ApSec2|<Scope>|<Base64>' string and returns the inner key-encrypted string (input for ConvertTo-SecureString -Key). #> param( [Parameter(Mandatory=$true)][String]$String ,[Parameter(Mandatory=$true)][Byte[]]$KeyBytes ) $parts = $String -split '\|', 3 If ( $parts.Count -ne 3 -or $parts[0] -ne 'ApSec2' -or $parts[1] -notin @('Machine','User') ) { throw "The string is not in a valid machine-bound format." } $scope = $parts[1] $protectedBytes = [Convert]::FromBase64String($parts[2]) If ( $PSVersionTable.PSEdition -eq 'Desktop' -or $IsWindows ) { If ( $PSVersionTable.PSEdition -eq 'Desktop' ) { Add-Type -AssemblyName System.Security } $dpapiScope = If ( $scope -eq 'User' ) { [System.Security.Cryptography.DataProtectionScope]::CurrentUser } else { [System.Security.Cryptography.DataProtectionScope]::LocalMachine } $plainBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($protectedBytes, $KeyBytes, $dpapiScope) } else { If ( $protectedBytes.Length -lt ( 16 + 32 + 1 ) ) { throw "The machine-bound payload is too short to be valid." } $context = Get-MachineIdentifier If ( $scope -eq 'User' ) { $context = "$( $context )|$( [System.Environment]::UserName )" } $kdf = [System.Security.Cryptography.HMACSHA256]::new($KeyBytes) $encKey = $kdf.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("enc|$( $context )")) $macKey = $kdf.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("mac|$( $context )")) $kdf.Dispose() $macOffset = $protectedBytes.Length - 32 $ivAndCipher = $protectedBytes[0..($macOffset - 1)] $mac = $protectedBytes[$macOffset..($protectedBytes.Length - 1)] # Verify the MAC before touching the ciphertext (constant-time comparison) $hmac = [System.Security.Cryptography.HMACSHA256]::new($macKey) $expectedMac = $hmac.ComputeHash([byte[]]$ivAndCipher) $hmac.Dispose() $diff = 0 For ( $i = 0; $i -lt 32; $i++ ) { $diff = $diff -bor ( $mac[$i] -bxor $expectedMac[$i] ) } If ( $diff -ne 0 ) { throw "Integrity check failed. The string was tampered with or belongs to another machine/user." } $aes = [System.Security.Cryptography.Aes]::Create() Try { $aes.Key = $encKey $aes.IV = [byte[]]$ivAndCipher[0..15] $decryptor = $aes.CreateDecryptor() $cipherBytes = [byte[]]$ivAndCipher[16..($ivAndCipher.Length - 1)] $plainBytes = $decryptor.TransformFinalBlock($cipherBytes, 0, $cipherBytes.Length) $decryptor.Dispose() } Finally { $aes.Dispose() } } [System.Text.Encoding]::UTF8.GetString($plainBytes) } |