Why does Windows CryptVerifySignature Fail on Signature created by PHP?

771 views Asked by At

I want to create a hash in PHP and sign it on a Linux server (using XAMPP on Windows for the testing) and then verify the hash on Windows using the public key. I have the following PHP script to generate it (I then right click, view source, copy/paste the signature create to the C/C++ Windows app). When I run the Windows app, the SHA256 hash matches the one created in PHP but the validation fails. I tried not byte swaping the signature in windows but same issue. What am I doing wrong?

Here is the PHP code:

<?php

$privatekey=<<<EOD
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC27fzjIIr/V3ds
1hLNyuyrpw5yAtTPG/deWyaU1kLHk028ohfkm9nbGFCloJNiIf51fSlpyPDiBjnC
pTCr/ac6TPGcXiJcxNvpzK9uFS2iQDgWHlJzvPT5dJacvg8RWXklVg3wseHMnp1t
mvCZrMDCiay4dAL0GWRyxUe12kX05XA4J0oSJbi11zwIq3SlhtzWaCn7njOLV0dh
BLidBhEY1Y47rjikCFaJrfac324NNvNuIJPI0gfhtqZFjjSC9vhUw0zE9SWl8iLb
3+qJ6HJ/Et+iPMGCmuvR4YB/UrdoN/pKQazfIfYoaRnoz0rT55uj42TL/mdhgWkO
3ftuocJtAgMBAAECggEBAKqq7z2YpxYDrNBGCdUmdhjQC5IjQhjYprnJoP1LyZIZ
xHUxZclL/r2CuftNDTSJMra6x2CCsPqvhEQtinNsfBDQqLqSuMyEfghrh2DVMXYn
JCy+rX591LDILZyfVb+CEpza5Lajv18AvS/9VmyJ2G2ntRWCZx8GcckCdh0cQBhy
QPKGmad2iBs0vcsP+2JTqxy1jMIPTydCVYwyXpTqqfUFvFWkeTQ8ZZT7mopOUqTL
wJBUTNwEbTCaoVMP/Ya57KjT0/3iD9r3n2IIOUsE1fIDJqFaoE0cXtERjTnkevTa
xJeuiqvVEd9+i2coTrpywLX6Ri9ATnYECg8XMW568gECgYEA8wqzxOM9TjV2yxyE
O3d6Fo6nVBpdFoj1S9Ies7SLwHEwIyF3/FmQB75jJs8uMhFNHbKpOiscOeKmtkEd
iSrN4qyQboNvBsHbL7nS0yF5zo9zRWH6RfAjV2Z+8cRNhN1rQqYrLZjDilo26B4c
VMmCoOGOKaPnYVMqAc9cPTmVZd0CgYEAwK7PRznksy19k8qRPZdtnlHMhNucNr0+
UvxK5MGV0aTvM5ZoJb75fT7YD5YN05FoMB4N4xO05S/l9ewM62zUOnTIRJM5wyDA
MEmTl5yxFv7CKpglAByc+hYsRQ+wES0GJ5p+yjFNP3kNANUIjIVQVWSJB6s6XsHL
qcFk3k4M7dECgYEA09AT9BeHKk32BfYIWDs8L8SLmASR9D3Qag/pqfxDf6glp7hX
dZooqFcI2p3dSM1DRAqc6ZEAvIIHgPcm4pBndpGmHmp1rJHukJ2GW3LlUVw7vdAx
6hnq8xTktZe7Z3wBKsjfAJIOeNk+2PTFBC/KpKNu3mN2F5//ECzD2qgAyBECgYEA
qKIVnMOpSbuddRRLSvlVocL7WKePL0Uu35gYv95BDcro4mBXY/mhBqFSnUl5blmL
MtmK09rybcce3r0pjX2gvExq3cFwQztNmLU8K+uh/XsXWgnwEAn6xNFVC1gwgsfB
5DVNlA3UCqIZ5EmOAtXGOgYT+5c7LaU0ZqxwaDMtE5ECgYB5j2uYxS75QgziesFs
ibIhmXYNhfZydpce5fhsIVZADOBTrj1Pkb9qoXAJgGgbuI17E+Mb5Adgs3W5YWHy
MJG9Ih8xQRM6Xau4EGF+I73JWcQHDxeoEYFi8nvTtb//hUMsME/gADyXgthTBPXS
fYx1VsZzfLe9U3YxOmrgM6fXIA==
-----END PRIVATE KEY-----
EOD;

$publickey=<<<EOD
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtu384yCK/1d3bNYSzcrs
q6cOcgLUzxv3XlsmlNZCx5NNvKIX5JvZ2xhQpaCTYiH+dX0pacjw4gY5wqUwq/2n
OkzxnF4iXMTb6cyvbhUtokA4Fh5Sc7z0+XSWnL4PEVl5JVYN8LHhzJ6dbZrwmazA
womsuHQC9BlkcsVHtdpF9OVwOCdKEiW4tdc8CKt0pYbc1mgp+54zi1dHYQS4nQYR
GNWOO644pAhWia32nN9uDTbzbiCTyNIH4bamRY40gvb4VMNMxPUlpfIi29/qiehy
fxLfojzBgprr0eGAf1K3aDf6SkGs3yH2KGkZ6M9K0+ebo+Nky/5nYYFpDt37bqHC
bQIDAQAB
-----END PUBLIC KEY-----
EOD;

    // $datatohash=$_POST["var1"].$_POST["var2"].$_POST["var3"];
    $datatohash="TestData";
    
    $hashvalue=hash("sha256", $datatohash);
  
    if (openssl_sign($hashvalue, $signature, $privatekey, OPENSSL_ALGO_SHA256)) {
        echo "Success: ", $hashvalue, "\n", base64_encode($signature), "\n\n";
        echo "BYTE Signature[]={ ";
        for ($i = 0; $i < strlen($signature); $i++) {
            echo "0x", dechex(ord($signature[$i])), ",";
        }/*
        for ($i = strlen($signature)-1; $i >=0 ; $i--) {
            echo "0x", dechex(ord($signature[$i])), ",";
        }*/
        echo "};\n";
        
        if (openssl_verify($hashvalue, $signature, $publickey, OPENSSL_ALGO_SHA256)) {
            echo "Verified\n";
        }
        else {
            echo "Verification Failed\n";
        }
    }
    else {
        echo "Failure:";
    }

?>

Here is the Windows C/C++ code:

// RSAVerify.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <windows.h>
#include <inttypes.h>

#include <stdio.h>

enum ePemType
{
  RSA_SIGNATURE, RSA_PRIVATE_KEY, RSA_PUBLIC_KEY
};


char PublicKey[]="-----BEGIN PUBLIC KEY-----\
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtu384yCK/1d3bNYSzcrs\
q6cOcgLUzxv3XlsmlNZCx5NNvKIX5JvZ2xhQpaCTYiH+dX0pacjw4gY5wqUwq/2n\
OkzxnF4iXMTb6cyvbhUtokA4Fh5Sc7z0+XSWnL4PEVl5JVYN8LHhzJ6dbZrwmazA\
womsuHQC9BlkcsVHtdpF9OVwOCdKEiW4tdc8CKt0pYbc1mgp+54zi1dHYQS4nQYR\
GNWOO644pAhWia32nN9uDTbzbiCTyNIH4bamRY40gvb4VMNMxPUlpfIi29/qiehy\
fxLfojzBgprr0eGAf1K3aDf6SkGs3yH2KGkZ6M9K0+ebo+Nky/5nYYFpDt37bqHC\
bQIDAQAB\
-----END PUBLIC KEY-----";

BYTE Signature[]={ 0x48,0x7d,0xeb,0xc,0x3c,0x6b,0x2e,0xd7,0x17,0x8d,0x9b,0x43,0xe2,0x29,0x97,0x8c,0x35,0x65,0x5a,0x41,0x89,0x4a,0x18,0x26,0x29,0x84,0x6f,0x1c,0xc3,0x9,0xcf,0x26,0x4,0x8d,0x2c,0xe0,0x3f,0xe6,0x73,0xc6,0x7e,0x94,0xee,0x5b,0x5e,0x20,0x4f,0x50,0xf,0x38,0x9c,0x63,0x78,0x89,0x7c,0x80,0x73,0xfb,0xf4,0x93,0x51,0x44,0xc6,0x2,0xd9,0x39,0xae,0xc2,0xb0,0xa4,0x19,0x95,0xe6,0x9,0x89,0x37,0x77,0x25,0x3a,0xd,0xe5,0xfe,0xc7,0x15,0x1d,0xab,0xd5,0xba,0x84,0xc8,0xa1,0xe2,0x97,0x5f,0x87,0x73,0xcd,0xbb,0x50,0x1c,0x4d,0x20,0x96,0x19,0x5f,0x1d,0xfe,0xc3,0xa7,0x58,0x1f,0x7,0xb9,0x2f,0xac,0x42,0x2,0x93,0x7a,0x68,0xb4,0x9f,0x5b,0xb2,0x9b,0x9c,0xcb,0x63,0xf3,0x4f,0xd0,0x20,0xc3,0x43,0x37,0x1c,0xb6,0x24,0x63,0x8b,0xe4,0xf7,0xf9,0x82,0x80,0x9,0xec,0x61,0xbf,0x5a,0xcc,0x3d,0xdf,0x7e,0x7b,0x88,0x18,0x7d,0x29,0x9c,0x7f,0x75,0xfb,0x11,0xdd,0x60,0xd3,0xfb,0x2a,0xc5,0xa4,0x5a,0xcd,0x26,0xe,0x13,0xa9,0x8,0x24,0x46,0xa9,0xdd,0xf4,0x0,0x34,0x76,0xac,0x71,0x9f,0xc1,0x12,0x88,0x64,0x43,0xb7,0xe3,0x39,0xa1,0xe1,0x50,0x7e,0x6c,0x6c,0xde,0x70,0xe1,0xd3,0x30,0xdf,0x3a,0x11,0x4,0x4a,0x24,0x32,0xd,0x67,0x56,0x8d,0x25,0x39,0x6c,0x40,0xf2,0x8a,0x85,0x67,0x41,0x29,0xa2,0xa1,0x2f,0x61,0xaa,0x59,0x18,0x60,0xbd,0x39,0x39,0xc2,0x9e,0xad,0x74,0xf7,0xee,0xcc,0xc0,0xd6,0x9c,0xc5,0x5c,0x80,0x47,0xe9,0xae,};

//-------------------------------------------------------------------------
// Purpose: Convert base64 key/certificate data to binary form
//
// Input:   base64data     - [i] the base64 data string
//          base64datasize - [i] the base64 data size (0 = z-term data)
//          flags          - [i] flags passed to CryptStringToBinary()
//          binsize        - [o] update with the size of binary data
//
// Output:  buffer to binary data or NULL if problem
//
// Notes:   caller must delete[] returned buffer.
//          flags will typically be CRYPT_STRING_ANY
//
BYTE* Base642Bin(const BYTE *base64data, DWORD base64datasize, DWORD flags, DWORD *binsize)
{
  BYTE * bindata=NULL;

  // calculate size required
  if (CryptStringToBinaryA((const char*) base64data, base64datasize, flags, NULL, binsize, NULL, NULL)) {
    // create buffer
    if ((bindata=new BYTE[*binsize])!=NULL) {
      // decode base64    
      if (!CryptStringToBinaryA((const char*) base64data, base64datasize, flags, bindata, binsize, NULL, NULL)) {
        // clean up.
        delete[] bindata;
        bindata=NULL;
      }
    }
  }
  // set size to zero if unable to decode to binary
  if (bindata==NULL) {
    *binsize=0;
  }
  // return buffer
  return bindata;
}


//-------------------------------------------------------------------------
// Purpose: Byte swap entire buffer (end == beginning)
//
// Input:   data      - [io] data buffer to swap
//          datasize  - [i] data buffer size
//
// Output:  na
//
// Notes:   
//
void ByteSwapBufferDirect(BYTE *data, DWORD datasize)
{
  BYTE *pa=data;
  BYTE *pb=data+datasize-1;

  for (DWORD i=0; i<datasize/2; i++, pa++, pb--) {
    BYTE t=*pa; 
    *pa=*pb;
    *pb=t;
  }
}

//-------------------------------------------------------------------------
// Purpose: Byte swap entire buffer (end == beginning)
//
// Input:   data      - [io] data buffer to swap
//          datasize  - [i] data buffer size
//
// Output:  na
//
// Notes:   
//
void ByteSwapBuffer(BYTE *dataout, const BYTE *datain, DWORD datasize)
{
  BYTE *pa=dataout;
  BYTE *pb=dataout+datasize-1;

  const BYTE *psa=datain;
  const BYTE *psb=datain+datasize-1;

  for (DWORD i=0; i<datasize/2; i++, pa++, pb--, psa++, psb--) {
    *pa=*psb;
    *pb=*psa;
  }
}

//-------------------------------------------------------------------------
// Purpose: Convert RSA PEM key/certificate/signature to DER format
//
// Input:   pemdata   - [i] pem data to convert
//          pemsize   - [i] size of pem data (0 = z-term)
//          pemtype   - [i] enumeration of PEM type 
//          dersize   - [o] size of returned der data
//
// Output:  buffer of derdata 
//
// Notes:   caller must delete[] the returned buffer.
//
BYTE *RSAPEMToDER(const BYTE *pemdata, DWORD pemsize, ePemType pemtype, DWORD *dersize)
{
  BYTE *derdata=Base642Bin(pemdata, pemsize, CRYPT_STRING_ANY, dersize);
  if (derdata) {
    // check if need to reverse bytes
    if (pemtype==RSA_SIGNATURE) {
      // yes, reverse from openssl MSB to Windows LSB
      ByteSwapBufferDirect(derdata, *dersize);
    }
  }

  return derdata;
}

//-------------------------------------------------------------------------
// Purpose: Convert RSA PEM key/certificate/signature to handle used by 
//          Windows crypt routines
//
// Input:   hcryptprovider - [i] handle to provider to assign key to
//          pemdata   - [i] pem data to convert
//          pemsize   - [i] size of pem data (0 = z-term)
//          pemtype   - [i] enumeration of PEM type 
//
// Output:  handle to the imported key or 0 if failure
//
// Notes:   The hcryptprovider is updated to contain the key
//
HCRYPTKEY ImportPEM(HCRYPTPROV hcryptprovider, const BYTE *pemdata, DWORD pemsize, ePemType pemtype)
{
  HCRYPTKEY hckey=0;

  DWORD dersize;
  BYTE *derdata=RSAPEMToDER(pemdata, pemsize, pemtype, &dersize);
  if (derdata) {
    if (pemtype==RSA_PUBLIC_KEY) {
      // convert DER to RSA public key info
      CERT_PUBLIC_KEY_INFO *keydata;
      DWORD keylen;
      if (CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
                              derdata, dersize, CRYPT_DECODE_ALLOC_FLAG, NULL, &keydata, &keylen)) {
        // import public key blob
        if (!CryptImportPublicKeyInfo(hcryptprovider, X509_ASN_ENCODING, (PCERT_PUBLIC_KEY_INFO) keydata, &hckey)) {
          // ensure zero
          hckey=0;
        }
        // clean up
        LocalFree(keydata);
      }
    }
    else {
      BYTE *keydata;
      DWORD keylen;
      PCRYPT_PRIVATE_KEY_INFO pki=0;
      DWORD pkisize;
      // convert PKCS8 data to private key info
      if (CryptDecodeObjectEx(X509_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, derdata, dersize,
                              CRYPT_DECODE_ALLOC_FLAG, NULL, &pki, &pkisize)) {
          // convert private key info to RSA private key blob
        if (CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY,
                                pki->PrivateKey.pbData, pki->PrivateKey.cbData, CRYPT_DECODE_ALLOC_FLAG,
                                NULL, &keydata, &keylen)) {
           // import private key blob
          if (!CryptImportKey(hcryptprovider, keydata, keylen, 0, CRYPT_EXPORTABLE, &hckey)) {
            hckey=NULL;
          }
          // clean up
          LocalFree(keydata);
        }
        // clean up
        LocalFree(pki);
      }
    }
  }
  // return key
  return hckey;
}


//-------------------------------------------------------------------------
// Purpose: Create SHA256 hash
//
// Input:   hcryptprovider - [i] handle to provider for hash
//          data      - [i] data to hash
//          datasize  - [i] size of data to hash
//
// Output:  hash handle
//
// Notes:   
//
HCRYPTHASH SHA256_hash(HCRYPTPROV hcryptprovider, const BYTE *data, DWORD datasize)
{
  HCRYPTHASH hash=0;
  // create hash object
  if (CryptCreateHash(hcryptprovider, CALG_SHA_256, 0, 0, &hash)) {
    // hash data contents
    if (!CryptHashData(hash, data, datasize, 0)) {
      // remove hash
      CryptDestroyHash(hash);
      hash=0;
    }
  }
  // return hash handle
  return hash;
}


//-------------------------------------------------------------------------
// Purpose: Verify hash 
//
// Input:   data      - [i] data to hash
//          datasize  - [i] size of data to hash
//          signature - [i] signature from linux
//          signaturesize - [i] size of signature in bytes
//          pempublickey - [i] public key
//          pempublickeysize - [i] size of public key or 0 if z-term
//
// Output:  TRUE/FALSE
//
// Notes:   
//
BOOL VerifyHash(const BYTE* data, DWORD datasize, const BYTE *signature, DWORD signaturesize, const BYTE *pempublickey, DWORD pempublickeysize)
{
  BOOL valid=FALSE;

  // Create provider
  HCRYPTPROV hcryptprov=0;
  if (CryptAcquireContext(&hcryptprov, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
    // assign public key to provider
    HCRYPTKEY pubkey=ImportPEM(hcryptprov, pempublickey, pempublickeysize, RSA_PUBLIC_KEY);
    if (pubkey) {
      // convert signature from MSB to LSB for windows
      BYTE *lsbsig;
      if ((lsbsig=new BYTE[signaturesize])!=NULL) {
        // do swap
        ByteSwapBuffer(lsbsig, signature, signaturesize);
        // create the hash
        HCRYPTHASH hash=SHA256_hash(hcryptprov, data, datasize);
        if (hash) {
         #if defined(_DEBUG)
          BYTE hashdata[32];
          DWORD hashdatasize=sizeof(hashdata);
          if (CryptGetHashParam(hash, HP_HASHVAL, hashdata, &hashdatasize, 0)) {
            printf("hash: ");
            for (UINT i=0; i<hashdatasize; i++) {
              printf("%02x", hashdata[i]);
            }
            printf("\n");
          }
         #endif
          // verify signature
          if (CryptVerifySignature(hash, lsbsig, signaturesize, pubkey, NULL, 0)) {
            valid=TRUE;
          }
          else {
            DWORD winec=GetLastError();
            printf("Verify Error (%xh)", winec);
            const char *str="";
            switch (winec) {
              case ERROR_INVALID_HANDLE:
                str="Invalid Handle";
                break;
              case ERROR_INVALID_PARAMETER:
                str="Invalid Handle";
                break;
              case NTE_BAD_FLAGS:
                str="Bad Flags";
                break;
              case NTE_BAD_HASH:
                str="Bad Hash";
                break;
              case NTE_BAD_KEY:
                str="Bad Key";
                break;
              case NTE_BAD_SIGNATURE:
                str="Bad Signature";
                break;
              case NTE_BAD_UID:
                str="Bad UID";
                break;
              case NTE_NO_MEMORY:
                str="No Memory";
                break;
            }
            printf("%s\n", str);
          }
          // clean up
          CryptDestroyHash(hash);
        }

        // clean up
        delete[] lsbsig;
      }
      // clean up
      CryptDestroyKey(pubkey);
    }
    // clean up
    CryptReleaseContext(hcryptprov, 0);
  }

  return valid;
}


int main()
{
  BYTE *datatohash=(BYTE*) "TestData"; 
  DWORD datatohashsize=(DWORD) strlen((char*)datatohash);

  if (VerifyHash(datatohash, datatohashsize, Signature, sizeof(Signature), (BYTE*) PublicKey, sizeof(PublicKey))) {
    printf("Verified!\n");
  }
  else {
    printf("Failed Verification!\n");
  }
}
1

There are 1 answers

0
RbMm On

you have logic error in php - openssl_sign function take string of data you wish to sign but not it hash.

instead

openssl_sign($hashvalue, $signature, $privatekey, OPENSSL_ALGO_SHA256)

you need

openssl_sign($datatohash, $signature, $privatekey, OPENSSL_ALGO_SHA256)

as result you calculate hash and it signature not from "TestData" but from "814d78962b0f8ac2bd63daf9f013ed0c07fe67fbfbfbc152b30a476304a0535d" which is sha256 hash of "TestData"

but in c++ code you calculate hash and signature from "TestData". i not check deep c++ code, probably it ok. but i be anyway use new Next Generation (CNG) Cryptography API instead old. old exist sense use only if you want support xp. anyway new api internal call new now. i be implement sign anf verify in next way:

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

NTSTATUS openssl_verify(_In_ BCRYPT_KEY_HANDLE hKey,
                        _In_ PCUCHAR pbToBeSigned, 
                        _In_ ULONG cbToBeSigned,
                        _In_ PCUCHAR pbSignature, 
                        _In_ ULONG cbSignature,
                        _In_ PCWSTR pszAlgId)
{
    BCRYPT_ALG_HANDLE hAlgorithm;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);

    if (0 <= status)
    {
        BCRYPT_HASH_HANDLE hHash = 0;

        ULONG HashBlockLength, cb;

        0 <= (status = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashBlockLength, sizeof(ULONG), &cb, 0)) &&
            0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0));

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);

        if (0 <= status)
        {
            PUCHAR pbHash = (PUCHAR)alloca(HashBlockLength);

            0 <= (status = BCryptHashData(hHash, const_cast<PUCHAR>(pbToBeSigned), cbToBeSigned, 0)) &&
                0 <= (status = BCryptFinishHash(hHash, pbHash, HashBlockLength, 0));

            BCryptDestroyHash(hHash);

            if (0 <= status)
            {
                BCRYPT_PKCS1_PADDING_INFO pi = { pszAlgId };

                status = BCryptVerifySignature(hKey, &pi, pbHash, HashBlockLength, 
                    const_cast<PUCHAR>(pbSignature), cbSignature, BCRYPT_PAD_PKCS1);
            }
        }
    }

    return status;
}

inline NTSTATUS openssl_verify(_In_ BCRYPT_KEY_HANDLE hKey,
                               _In_ PCSTR szToBeSigned,
                               _In_ PCUCHAR pbSignature, 
                               _In_ ULONG cbSignature,
                               _In_ PCWSTR pszAlgId)
{
    return openssl_verify(hKey, (PCUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), pbSignature, cbSignature, pszAlgId);
}

NTSTATUS openssl_sign(_In_ BCRYPT_KEY_HANDLE hKey,
                      _In_ PCUCHAR pbToBeSigned, 
                      _In_ ULONG cbToBeSigned,
                      _Out_ PUCHAR pbSignature, 
                      _Inout_ PULONG pcbSignature,
                      _In_ PCWSTR pszAlgId)
{
    BCRYPT_ALG_HANDLE hAlgorithm;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);

    if (0 <= status)
    {
        BCRYPT_HASH_HANDLE hHash = 0;

        ULONG HashBlockLength, cb;

        0 <= (status = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashBlockLength, sizeof(ULONG), &cb, 0)) &&
            0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0));

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);

        if (0 <= status)
        {
            PUCHAR pbHash = (PUCHAR)alloca(HashBlockLength);

            0 <= (status = BCryptHashData(hHash, const_cast<PUCHAR>(pbToBeSigned), cbToBeSigned, 0)) &&
                0 <= (status = BCryptFinishHash(hHash, pbHash, HashBlockLength, 0));

            BCryptDestroyHash(hHash);

            if (0 <= status)
            {
                BCRYPT_PKCS1_PADDING_INFO pi = { pszAlgId };

                status = BCryptSignHash(hKey, &pi, pbHash, HashBlockLength, 
                    pbSignature, *pcbSignature, pcbSignature, BCRYPT_PAD_PKCS1);
            }
        }
    }

    return status;
}

inline NTSTATUS openssl_sign(_In_ BCRYPT_KEY_HANDLE hKey,
                             _In_ PCSTR szToBeSigned,
                             _Out_ PUCHAR pbSignature, 
                             _Inout_ PULONG pcbSignature,
                             _In_ PCWSTR pszAlgId)
{
    return openssl_sign(hKey, (PCUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), pbSignature, pcbSignature, pszAlgId);
}

NTSTATUS BCryptImportKey(_Out_ BCRYPT_KEY_HANDLE *phKey, 
                         _In_ PCWSTR pszBlobType, 
                         _In_ BCRYPT_RSAKEY_BLOB* prkb, 
                         _In_ ULONG cb)
{
    BCRYPT_ALG_HANDLE hAlgorithm;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RSA_ALGORITHM, 0, 0);

    if (0 <= status)
    {
        status = BCryptImportKeyPair(hAlgorithm, 0, pszBlobType, phKey, (PUCHAR)prkb, cb, 0);

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    }

    return status;
}

HRESULT BCryptImportPrivateKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCUCHAR pbKey, _In_ ULONG cbKey)
{
    ULONG cb;
    PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;

    ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, 
        pbKey, cbKey, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, (void**)&PrivateKeyInfo, &cb));

    if (dwError == NOERROR)
    {
        BCRYPT_RSAKEY_BLOB* prkb;

        dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            CNG_RSA_PRIVATE_KEY_BLOB, PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData, 
            CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&prkb, &cb));

        LocalFree(PrivateKeyInfo);

        if (dwError == NOERROR)
        {
            NTSTATUS status = BCryptImportKey(phKey, BCRYPT_RSAPRIVATE_BLOB, prkb, cb);
            LocalFree(prkb);
            return HRESULT_FROM_NT(status);
        }
    }

    return HRESULT_FROM_WIN32(dwError);
}

HRESULT BCryptImportPublicKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCUCHAR pbKeyOrCert, _In_ ULONG cbKeyOrCert, bool bCert)
{
    ULONG cb;

    union {
        PVOID pvStructInfo;
        PCERT_INFO pCertInfo;
        PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
    };

    ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
        bCert ? X509_CERT_TO_BE_SIGNED : X509_PUBLIC_KEY_INFO, 
        pbKeyOrCert, cbKeyOrCert, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, &pvStructInfo, &cb));

    if (dwError == NOERROR)
    {
        BCRYPT_RSAKEY_BLOB* prkb;

        PVOID pv = pvStructInfo;

        if (bCert)
        {
            PublicKeyInfo = &pCertInfo->SubjectPublicKeyInfo;
        }

        dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            CNG_RSA_PUBLIC_KEY_BLOB, 
            PublicKeyInfo->PublicKey.pbData, 
            PublicKeyInfo->PublicKey.cbData, 
            CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&prkb, &cb));

        LocalFree(pv);

        if (dwError == NOERROR)
        {
            NTSTATUS status = BCryptImportKey(phKey, BCRYPT_RSAPUBLIC_BLOB, prkb, cb);
            LocalFree(prkb);
            return HRESULT_FROM_NT(status);
        }
    }

    return HRESULT_FROM_WIN32(dwError);
}

enum BLOB_TYPE { bt_priv, bt_pub, bt_cert };

HRESULT BCryptImportKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ BLOB_TYPE bt, _In_ PCSTR szKey, _In_ ULONG cchKey)
{
    PUCHAR pbKey = 0;
    ULONG cbKey = 0;
    HRESULT hr;

    while (CryptStringToBinaryA(szKey, cchKey, CRYPT_STRING_BASE64HEADER, pbKey, &cbKey, 0, 0))
    {
        if (pbKey)
        {
            switch (bt)
            {
            case bt_priv:
                hr = BCryptImportPrivateKey(phKey, pbKey, cbKey);
                break;
            case bt_pub:
                hr = BCryptImportPublicKey(phKey, pbKey, cbKey, false);
                break;
            case bt_cert:
                hr = BCryptImportPublicKey(phKey, pbKey, cbKey, true);
                break;
            default: hr = E_INVALIDARG;
            }

            _freea(pbKey);

            return hr;
        }

        if (!(pbKey = (PUCHAR)_malloca(cbKey)))
        {
            break;
        }
    }

    hr = HRESULT_FROM_WIN32(GetLastError());

    if (pbKey) _freea(pbKey);

    return hr;
}

HRESULT Verify_Signature(_In_ PCSTR szToBeSigned, 
                         _In_ PCSTR szPublicKeyOrCert, 
                         _In_ ULONG cchPublicKeyOrCert, 
                         _In_ PCUCHAR pbSignature, 
                         _In_ ULONG cbSignature,
                         _In_ bool bCert,
                         _In_ PCWSTR pszAlgId = BCRYPT_SHA256_ALGORITHM)
{
    HRESULT hr;
    BCRYPT_KEY_HANDLE hKey;

    if (0 <= (hr = BCryptImportKey(&hKey, bCert ? bt_cert : bt_pub, szPublicKeyOrCert, cchPublicKeyOrCert)))
    {
        hr = HRESULT_FROM_NT(openssl_verify(hKey, szToBeSigned, pbSignature, cbSignature, pszAlgId));

        BCryptDestroyKey(hKey);
    }

    return hr;
}

HRESULT Create_Signature(_In_ PCSTR szToBeSigned, 
                         _In_ PCSTR szPrivateKey, 
                         _In_ ULONG cchPrivateKey,
                         _Out_ UCHAR** ppbSignature,
                         _Out_ ULONG* pcbSignature,
                         _In_ PCWSTR pszAlgId = BCRYPT_SHA256_ALGORITHM)
{
    HRESULT hr;
    BCRYPT_KEY_HANDLE hKey;

    if (0 <= (hr = BCryptImportKey(&hKey, bt_priv, szPrivateKey, cchPrivateKey)))
    {
        ULONG cbSignature, cb;

        if (0 <= (hr = BCryptGetProperty(hKey, BCRYPT_SIGNATURE_LENGTH, (PUCHAR)&cbSignature, sizeof(ULONG), &cb, 0)))
        {
            if (PUCHAR pbSignature = new UCHAR[cbSignature])
            {
                if (0 <= (hr = HRESULT_FROM_NT(openssl_sign(hKey, szToBeSigned, pbSignature, &cbSignature, pszAlgId))))
                {
                    *pcbSignature = cbSignature, *ppbSignature = pbSignature;
                }
                else
                {
                    delete [] pbSignature;
                }
            }
        }
        BCryptDestroyKey(hKey);
    }

    return hr;
}

void SignTest()
{
    char TestToBeSigned[] = "814d78962b0f8ac2bd63daf9f013ed0c07fe67fbfbfbc152b30a476304a0535d";

    PUCHAR pbSignature;
    ULONG cbSignature;

    if (0 <= Create_Signature(TestToBeSigned, PrivateKey, _countof(PrivateKey) - 1, &pbSignature, &cbSignature))
    {
        if (0 > Verify_Signature(TestToBeSigned, PublicKey, _countof(PublicKey) - 1, pbSignature, cbSignature, false))
        {
            __debugbreak();
        }

        ULONG i = 0;

        DbgPrint("const UCHAR Signature[] = {");

        do 
        {
            if (!(i++ & 7)) DbgPrint("\n\t");
            DbgPrint("0x%02x, ", pbSignature[i]);
        } while (--cbSignature);

        DbgPrint("\n};\n");

        delete [] pbSignature;
    }
}

with "814d78962b0f8ac2bd63daf9f013ed0c07fe67fbfbfbc152b30a476304a0535d" string i got correct result and exactly

const UCHAR Signature[] = {
    0x48, 0x7d, 0xeb, 0x0c, 0x3c, 0x6b, 0x2e, 0xd7, 
    0x17, 0x8d, 0x9b, 0x43, 0xe2, 0x29, 0x97, 0x8c, 
    0x35, 0x65, 0x5a, 0x41, 0x89, 0x4a, 0x18, 0x26, 
    0x29, 0x84, 0x6f, 0x1c, 0xc3, 0x09, 0xcf, 0x26, 
    0x04, 0x8d, 0x2c, 0xe0, 0x3f, 0xe6, 0x73, 0xc6, 
    0x7e, 0x94, 0xee, 0x5b, 0x5e, 0x20, 0x4f, 0x50, 
    0x0f, 0x38, 0x9c, 0x63, 0x78, 0x89, 0x7c, 0x80, 
    0x73, 0xfb, 0xf4, 0x93, 0x51, 0x44, 0xc6, 0x02, 
    0xd9, 0x39, 0xae, 0xc2, 0xb0, 0xa4, 0x19, 0x95, 
    0xe6, 0x09, 0x89, 0x37, 0x77, 0x25, 0x3a, 0x0d, 
    0xe5, 0xfe, 0xc7, 0x15, 0x1d, 0xab, 0xd5, 0xba, 
    0x84, 0xc8, 0xa1, 0xe2, 0x97, 0x5f, 0x87, 0x73, 
    0xcd, 0xbb, 0x50, 0x1c, 0x4d, 0x20, 0x96, 0x19, 
    0x5f, 0x1d, 0xfe, 0xc3, 0xa7, 0x58, 0x1f, 0x07, 
    0xb9, 0x2f, 0xac, 0x42, 0x02, 0x93, 0x7a, 0x68, 
    0xb4, 0x9f, 0x5b, 0xb2, 0x9b, 0x9c, 0xcb, 0x63, 
    0xf3, 0x4f, 0xd0, 0x20, 0xc3, 0x43, 0x37, 0x1c, 
    0xb6, 0x24, 0x63, 0x8b, 0xe4, 0xf7, 0xf9, 0x82, 
    0x80, 0x09, 0xec, 0x61, 0xbf, 0x5a, 0xcc, 0x3d, 
    0xdf, 0x7e, 0x7b, 0x88, 0x18, 0x7d, 0x29, 0x9c, 
    0x7f, 0x75, 0xfb, 0x11, 0xdd, 0x60, 0xd3, 0xfb, 
    0x2a, 0xc5, 0xa4, 0x5a, 0xcd, 0x26, 0x0e, 0x13, 
    0xa9, 0x08, 0x24, 0x46, 0xa9, 0xdd, 0xf4, 0x00, 
    0x34, 0x76, 0xac, 0x71, 0x9f, 0xc1, 0x12, 0x88, 
    0x64, 0x43, 0xb7, 0xe3, 0x39, 0xa1, 0xe1, 0x50, 
    0x7e, 0x6c, 0x6c, 0xde, 0x70, 0xe1, 0xd3, 0x30, 
    0xdf, 0x3a, 0x11, 0x04, 0x4a, 0x24, 0x32, 0x0d, 
    0x67, 0x56, 0x8d, 0x25, 0x39, 0x6c, 0x40, 0xf2, 
    0x8a, 0x85, 0x67, 0x41, 0x29, 0xa2, 0xa1, 0x2f, 
    0x61, 0xaa, 0x59, 0x18, 0x60, 0xbd, 0x39, 0x39, 
    0xc2, 0x9e, 0xad, 0x74, 0xf7, 0xee, 0xcc, 0xc0, 
    0xd6, 0x9c, 0xc5, 0x5c, 0x80, 0x47, 0xe9, 0xae, 
};