misc/dpapi-working.ps1

function Lock-DPAPIMessage {
    <#
    .SYNOPSIS
        Encrypts a message using DPAPI-NG and a target SID
    .DESCRIPTION
        DPAPI-NG allows encrypting messages using a target SID, which can then be decrypted only by them.
    .NOTES
        This is not directly supported in Linux, but there are python implementations (see LAPS4Linux)
    .LINK
        https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi
    .EXAMPLE
        Test-MyTestFunction -Verbose
        Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
    #>



    [CmdletBinding(DefaultParameterSetName = 'SIDString')]
    param (
        # Security Principal to use for encrypting the message
        [Parameter(Mandatory, ParameterSetName = "SIDString")]
        [Parameter(Mandatory, ParameterSetName = "SIDBytes")]
        [String]$SID,

        # Message to Encrypt, in String format
        [Parameter(Mandatory, ParameterSetName = "SIDString")]
        [String]$String,

        # Message to encrypt, in Byte array format
        [Parameter(Mandatory, ParameterSetName = "SIDBytes")]
        [Byte[]]$Bytes
    )

    begin {
    }

    process {
        if ($String) {
            write-loghandler -level "Verbose" -message "Converting message to Byte[]"
            [Byte[]]$Bytes = [system.text.encoding]::UTF8.getBytes($String)
        }

        if ($SID) {
            $Descriptor = "SID=$SID"
            write-loghandler -level "Verbose" -message "Setting descriptor as $Descriptor"
        }
        $protected = [DpapiNgUtil]::Protect($descriptor,$Bytes)
        if ($AsBase64) {
            [system.convert]::ToBase64String($protected)
        } else {
            $Protected
        }
<#
 
 
$secretJSON = $secret | ConvertTo-Json
$protected = [DpapiNgUtil]::Protect($descriptor,[system.text.encoding]::UTF8.getBytes($SecretJSON))
$protectedB64 = [system.convert]::ToBase64String($protected)
"{0:-25} - {1}" -f "Original String", $SecretText
"{0:-25} - {1}" -f "DPAPI String", $ProtectedB64
$unprotected = $null
$unprotected = [system.text.encoding]::UTF8.GetString([DpapiNgUtil]::Unprotect($protected))
$Unprotected
#>

    }

    end {

    }
}

function new-LAPSPasswordJSON {
    <#
    .SYNOPSIS
        Creates a plaintext LAPS JSON string
    .DESCRIPTION
        LAPS Passwords need to be in JSON format before being protected by DPAPI. This takes a username and a password and converts it to the correct format.
    .NOTES
        This functionality on Linux can be seen with LAPS4Linux.
        For reference, see LAPS Tech ref: https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-technical-reference
    .LINK
        Specify a URI to a help page, this will show when Get-Help -Online is used.
    .EXAMPLE
        Test-MyTestFunction -Verbose
        Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
    #>


    [CmdletBinding(DefaultParameterSetName="Prompt")]
    param (
        [Parameter(ParameterSetName = "Credential", Mandatory )]
        [ValidateScript( {
            -not [String]::IsNullOrEmpty($_.UserName) -and
            -not [String]::IsNullOrEmpty($_.GetNetworkCredential().Password)
        })]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(ParameterSetName = "UserWithPass", Mandatory )]
        [Parameter(ParameterSetName = "UserGenPass", Mandatory )]
        [Parameter(ParameterSetName = "Prompt" )]
        [ValidateNotNullOrEmpty()]
        [String]$Username,

        [Parameter(ParameterSetName = "UserWithPass", Mandatory)]
        [ValidateNotNullOrEmpty()]
        [securestring]$Password,

        [Parameter(ParameterSetName = "UserGenPass", Mandatory)]
        [Switch]$GeneratePassword,


        [ValidateNotNullOrEmpty()]
        [string]$LastSet,

        # Output as plain JSON instead of UTF-16-LE with trailing nulls
        [Switch]$AsPlainText,

        # Output as plain JSON instead of UTF-16-LE with trailing nulls
        [Switch]$IncludeUnencryptedPassword
    )

    begin {
    }

    process {
        write-loghandler -level "Verbose" -message "Function Invocation Details"
        write-loghandler -level "Verbose" -message ("{0,-15} ; {1,-15} ; {2,-15} ; {3,-15}" -f "ParameterSet", "WhatIf", "Confirm", "Verbose")
        write-loghandler -level "Verbose" -message ("{0,-15} ; {1,-15} ; {2,-15} ; {3,-15}" -f $PsCmdlet.ParameterSetName, $WhatIfPreference, $ConfirmPreference, $VerbosePreference)
        if ($GeneratePassword) {
            [SecureString]$Password = get-randomPassword -forceComplex
        }
        if (-not $credential) {
            if ($username -and $Password) {
                $Credential = New-Object System.Management.Automation.PSCredential($username, $Password)
            } else {
                $CredParams = @{
                    Message = "Enter the LAPS credential to be stored"
                }
                if ($username) {
                    $CredParams.Username = $Username
                }
                $Credential = Get-Credential @CredParams
            }
        }
        if (
            [String]::IsNullOrEmpty($Credential.UserName) -or
            [String]::IsNullOrEmpty($Credential.GetNetworkCredential().Password)
        ) {
            Throw "Credential username or a password was either null, blank, or not provided."
        }

        if (-not $LastSet) {
            $LastSet = [datetime]::now
        }
        $LastSetFileTime = [datetime]::Parse($lastSet).ToFileTimeUtc()
        $LastSetLAPSFormat = $LastSetFileTime.ToString("X").toLower()

        if ($IncludeUnencryptedPassword) {
            $LapsPw = $Credential.GetNetworkCredential().password
        } else {
            write-loghandler -level "Verbose" -message "Blinding password because 'IncludeUnencryptedPassword' not specified"
            $LAPSPw = $cred.password.GetHashCode()
        }

        $LAPSHashTable = @{
            'n' = $Credential.UserName
            'p' = $LapsPw
            't' = "$LastSetLAPSFormat"
        }
        $LAPSJSON = $LAPSHashTable | convertto-JSON -Compress
        if ($AsPlainText) {
            $Output = $LAPSJSON
        } else {
            write-loghandler -level "Verbose" -message "UTF-16-LE + NUL NUL"
            $LAPSPadding=[system.text.encoding]::UTF8.GetBytes(@(0x00, 0x00) -as [Char[]])
            $Output = [system.text.encoding]::Unicode.GetBytes($LAPSJSON) + $LAPSPadding
        }
        $Output
    }

    end {

    }
}

function get-randomPassword {
    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateRange(6,128)]
        [Int]
        $PasswordLength = 14,

        [switch]
        $forceComplex,

        [Switch]
        $AsPlainText

    )
    BEGIN {
        [char[]]$Exclusions = 'vwuOmnil1-.,%'
    }
    PROCESS{
        $GeneratedCharacters = @{
            Uppercase = (97..122) | get-random -count 32 | where-object {$Exclusions -notContains $_} | foreach-object {[Char]$_}
            Lowercase = (65..90) | get-random -count 128 | where-object {$Exclusions -notContains $_} | foreach-object {[Char]$_}
            Numeric = (48..57) | get-random -count 16 | where-object {$Exclusions -notContains $_} | foreach-object {[Char]$_}
            SpecialChar = [Char[]]('!!@#$%&*()=?}][{') | get-random -count 4 | where-object {$Exclusions -notContains $_} | foreach-object {[Char]$_}
        }

        $StringSet = $GeneratedCharacters.Uppercase + $GeneratedCharacters.Lowercase + $GeneratedCharacters.Numeric + $GeneratedCharacters.SpecialChar
        $Complexity = get-random -count 1 -inputObject $GeneratedCharacters.Lowercase
        $Complexity += get-random -count 1 -inputObject $GeneratedCharacters.Uppercase
        $Complexity += get-random -count 1 -inputObject $GeneratedCharacters.Numeric
        $Complexity += get-random -count 1 -inputObject $GeneratedCharacters.SpecialChar

        $PreScramble = -join(get-random -count ($passwordLength-4) -InputObject $StringSet)
        $GeneratedPassword = $($PreScramble + $Complexity) | sort-object {get-random}
        if ($AsPlainText) {
            $GeneratedPassword
        } else {
            $GeneratedPassword | ConvertTo-SecureString -AsPlainText -force
        }
    }
}



$DPAPI_TypeDef = @"
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
 
public static class DpapiNgUtil
{
    public static string ProtectBase64(string protectionDescriptor, string input)
    {
        byte[] output = Protect(protectionDescriptor, Encoding.UTF8.GetBytes(input));
        return Convert.ToBase64String(output);
    }
 
    public static string UnprotectBase64(string input)
    {
        byte[] bytes = Convert.FromBase64String(input);
        byte[] output = Unprotect(bytes);
        return Encoding.UTF8.GetString(output);
    }
 
    public static byte[] Protect(string protectionDescriptor, byte[] data)
    {
        using (NCryptProtectionDescriptorHandle handle = NCryptProtectionDescriptorHandle.Create(protectionDescriptor))
        {
            return Protect(handle, data);
        }
    }
 
    internal static byte[] Protect(NCryptProtectionDescriptorHandle descriptor, byte[] data)
    {
        uint cbProtectedBlob;
        LocalAllocHandle protectedBlobHandle;
        int status = NativeMethods.NCryptProtectSecret(descriptor, NativeMethods.NCRYPT_SILENT_FLAG, data, (uint)data.Length, IntPtr.Zero, IntPtr.Zero, out protectedBlobHandle, out cbProtectedBlob);
        if(status != 0)
        {
            throw new CryptographicException(status);
        }
 
        using (protectedBlobHandle)
        {
            byte[] retVal = new byte[cbProtectedBlob];
            Marshal.Copy(protectedBlobHandle.DangerousGetHandle(), retVal, 0, retVal.Length);
            return retVal;
        }
    }
 
    public static byte[] Unprotect(byte[] protectedData)
    {
        uint cbData;
        LocalAllocHandle dataHandle;
        int status = NativeMethods.NCryptUnprotectSecret(IntPtr.Zero, NativeMethods.NCRYPT_SILENT_FLAG, protectedData, (uint)protectedData.Length, IntPtr.Zero, IntPtr.Zero, out dataHandle, out cbData);
        if (status != 0)
        {
            throw new CryptographicException(status);
        }
 
        using (dataHandle)
        {
            byte[] retVal = new byte[cbData];
            Marshal.Copy(dataHandle.DangerousGetHandle(), retVal, 0, retVal.Length);
            return retVal;
        }
    }
}
 
internal class LocalAllocHandle : SafeHandle
{
    // Called by P/Invoke when returning SafeHandles
    private LocalAllocHandle() : base(IntPtr.Zero, ownsHandle: true) { }
 
    // Do not provide a finalizer - SafeHandle's critical finalizer will
    // call ReleaseHandle for you.
 
    public override bool IsInvalid
    {
        get { return handle == IntPtr.Zero; }
    }
 
    protected override bool ReleaseHandle()
    {
        IntPtr retVal = NativeMethods.LocalFree(handle);
        return (retVal == IntPtr.Zero);
    }
}
 
internal class NCryptProtectionDescriptorHandle : SafeHandle
{
    // Called by P/Invoke when returning SafeHandles
    private NCryptProtectionDescriptorHandle() : base(IntPtr.Zero, ownsHandle: true) { }
 
    // Do not provide a finalizer - SafeHandle's critical finalizer will
    // call ReleaseHandle for you.
 
    public override bool IsInvalid
    {
        get { return handle == IntPtr.Zero; }
    }
 
    public static NCryptProtectionDescriptorHandle Create(string protectionDescriptor)
    {
        NCryptProtectionDescriptorHandle descriptorHandle;
        int status = NativeMethods.NCryptCreateProtectionDescriptor(protectionDescriptor, 0, out descriptorHandle);
        if (status != 0) {
            throw new CryptographicException(status);
        }
        return descriptorHandle;
    }
 
    protected override bool ReleaseHandle()
    {
        int retVal = NativeMethods.NCryptCloseProtectionDescriptor(handle);
        return (retVal == 0);
    }
}
 
internal static class NativeMethods
{
    private const string KERNEL32LIB = "kernel32.dll";
    private const string NCRYPTLIB = "ncrypt.dll";
 
    internal const uint NCRYPT_SILENT_FLAG = 0x00000040;
 
 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366730(v=vs.85).aspx
    [DllImport(KERNEL32LIB, SetLastError = true)]
    internal static extern IntPtr LocalFree(
        [In] IntPtr handle);
 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706799(v=vs.85).aspx
    [DllImport(NCRYPTLIB)]
    internal extern static int NCryptCloseProtectionDescriptor(
        [In] IntPtr hDescriptor);
 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706800(v=vs.85).aspx
    [DllImport(NCRYPTLIB, CharSet = CharSet.Unicode)]
    internal extern static int NCryptCreateProtectionDescriptor(
        [In] string pwszDescriptorString,
        [In] uint dwFlags,
        [Out] out NCryptProtectionDescriptorHandle phDescriptor);
 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706802(v=vs.85).aspx
    [DllImport(NCRYPTLIB)]
    internal extern static int NCryptProtectSecret(
        [In] NCryptProtectionDescriptorHandle hDescriptor,
        [In] uint dwFlags,
        [In] byte[] pbData,
        [In] uint cbData,
        [In] IntPtr pMemPara,
        [In] IntPtr hWnd,
        [Out] out LocalAllocHandle ppbProtectedBlob,
        [Out] out uint pcbProtectedBlob);
 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706811(v=vs.85).aspx
    [DllImport(NCRYPTLIB)]
    internal extern static int NCryptUnprotectSecret(
        [In] IntPtr phDescriptor,
        [In] uint dwFlags,
        [In] byte[] pbProtectedBlob,
        [In] uint cbProtectedBlob,
        [In] IntPtr pMemPara,
        [In] IntPtr hWnd,
        [Out] out LocalAllocHandle ppbData,
        [Out] out uint pcbData);
}
"@

add-type -TypeDefinition $DPAPI_TypeDef