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");
}
}
derdata
theCryptDecodeObjectEx
fails. If I byte-swap the keydata (thekeydata->PublicKey.pbData
) theCryptImportPublicKeyInfo
fails. I added a simple checksum check of the byte-swaped data in my version to ensure the checksum matches before and after and it does. – CeylonCryptVerifySignature
? There is also a sample on github to general RSA key/sign files/verify files, and I use this sample to generate a signature with the same privatekey and publickey as yours, it works for me to verify with your C++ sample. – HummockTextData
in it and ran rsa-master -s private.pem -x sig.bin testdata.txt then changed the php script to output the signature to a file as wellfopen() fwrite() fclose()
, the two files have nothing in common, not reversed, nothing. – Ceylonopenssl_sign($hashvalue, $signature, $privatekey, OPENSSL_ALGO_SHA256)
to beopenssl_sign($datatohash, $signature, $privatekey, OPENSSL_ALGO_SHA256)
it matches. So I guess there is an internal hash used as part of the signature creation and nothing to do with a separate hash on the data. – Ceylon