Today I want to present another useful CryptoAPI functions to use when working with PFX (PKCS#12) certificates.

  1. Determine if the BLOB is PFX without having to pass a password;
  2. Test PFX password.

Of course, you can try to use appropriate X509Certificate2 class constructor, but this approach is faster and do not require key import in cryptographic provider and other actions performed by X509Certificate2 constructor. This functionality is implemented in two CryptoAPI functions:

  1. PFXIsPFXBlob
  2. PFXVerifyPassword

And here is complete solution:

$signature = @"
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool PFXIsPFXBlob(
    CRYPTOAPI_BLOB pPFX
);
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool PFXVerifyPassword(
    CRYPTOAPI_BLOB pPFX,
    [MarshalAs(UnmanagedType.LPWStr)]
    string szPassword,
    int dwFlags
);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPTOAPI_BLOB {
    public int cbData;
    public IntPtr pbData;
}
"@
Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32

function Test-CertIsPfx ([Byte[]]$RawData) {
    # create buffer in an unmanaged memory to store raw data
    $pbData = [Runtime.InteropServices.Marshal]::AllocHGlobal($RawData.Length)
    # copy bytes to unmanaged buffer:
    [Runtime.InteropServices.Marshal]::Copy($RawData,0,$pbData,$RawData.Length)
    # create CRYPTOAPI_BLOB structure
    $blob = New-Object PKI.Crypt32+CRYPTOAPI_BLOB -Property @{
        cbData = $RawData.Length
        pbData = $pbData
    }
    # call the PFXVerifyPassword to test the content. The function returns $true
    # if byte array represents PFX file.
    [PKI.Crypt32]::PFXIsPFXBlob($blob)
    # release unmanaged buffer:
    [Runtime.InteropServices.Marshal]::FreeHGlobal($pbData)
}
function Test-PfxPassword ([Byte[]]$RawData, [string]$Password) {
    # create buffer in an unmanaged memory to store raw data
    $pbData = [Runtime.InteropServices.Marshal]::AllocHGlobal($RawData.Length)
    # copy bytes to unmanaged buffer:
    [Runtime.InteropServices.Marshal]::Copy($RawData,0,$pbData,$RawData.Length)
    # create CRYPTOAPI_BLOB structure
    $blob = New-Object PKI.Crypt32+CRYPTOAPI_BLOB -Property @{
        cbData = $RawData.Length
        pbData = $pbData
    }
    # call the PFXVerifyPassword to test the password. The function returns $true
    # if password is correct
    [PKI.Crypt32]::PFXVerifyPassword($blob, $Password, 0)
    # release unmanaged buffer:
    [Runtime.InteropServices.Marshal]::FreeHGlobal($pbData)
}

and usage:

PS C:\> $bytes = [io.file]::ReadAllBytes(".\3.pfx")
PS C:\> $bytes2 = [io.file]::ReadAllBytes(".\pem.txt")
PS C:\> Test-CertIsPfx $bytes
True
PS C:\> Test-CertIsPfx $bytes2
False
PS C:\> Test-PfxPassword $bytes "1"
True
PS C:\> Test-PfxPassword $bytes "other password"
False
PS C:\>

PFX files must be stored in a pure binary format. Unlike regular certificates, PFX do not support base64 encoding in files.

In these examples, I read two files, one PFX, another – “whateverelse” and passed them to Test-CertIsPfx function. As you see, only bytes from “3.pfx” file returned $true. And attempted to guess the password and guessed from the first attempt!!! (joke). This is something you can write during morning coffee/tea :)


Share this article:

Comments:


Post your comment:

Please, solve this little equation and enter result below. Captcha