Search code examples
c++hashcryptographyhmachmacsha1

How do I replicate PHP's hash_hmac function in C++?


I'm trying to replicate PHP's hash_hmac function in C++ using wincrypt. Someone sent me this MS link titled "Example C Program: Creating an HMAC". I copy all of the code and the result prints out

The hash is: 48 f2 57 38 29 29 43 16 fd f4 db 58 31 e1 0c 74 48 8e d4 e2

The sample data in this case uses "password" and "message". So now I switch over to the PHP function and I call it two ways to make sure the order is right:

echo(hash_hmac('sha1', "password", "message", false));
echo PHP_EOL;
echo(hash_hmac('sha1', "message", "password", false));

And this prints out

f5959d1d5c133fd9368218e1ffde3075382d7fd7
4c2de361ba8958558de3d049ed1fb5c115656e65

So none of the hashes are matching. What is the difference here? How do I get the C++ one to match the PHP?

I'm being forced to edit this question to explain why this question doesn't help me. The two answers are "use openssl" and "it's impossible". Firstly, openssl isn't an option. Second, it IS possible. It has to be, because PHP is written in C++ as are other third party libraries that do it...

Full code below in case the MS page dies someday.

#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

int main()
{
//--------------------------------------------------------------------
// Declare variables.
//
// hProv:           Handle to a cryptographic service provider (CSP). 
//                  This example retrieves the default provider for  
//                  the PROV_RSA_FULL provider type.  
// hHash:           Handle to the hash object needed to create a hash.
// hKey:            Handle to a symmetric key. This example creates a 
//                  key for the RC4 algorithm.
// hHmacHash:       Handle to an HMAC hash.
// pbHash:          Pointer to the hash.
// dwDataLen:       Length, in bytes, of the hash.
// Data1:           Password string used to create a symmetric key.
// Data2:           Message string to be hashed.
// HmacInfo:        Instance of an HMAC_INFO structure that contains 
//                  information about the HMAC hash.
// 
HCRYPTPROV  hProv       = NULL;
HCRYPTHASH  hHash       = NULL;
HCRYPTKEY   hKey        = NULL;
HCRYPTHASH  hHmacHash   = NULL;
PBYTE       pbHash      = NULL;
DWORD       dwDataLen   = 0;
BYTE        Data1[]     = {0x70,0x61,0x73,0x73,0x77,0x6F,0x72,0x64};
BYTE        Data2[]     = {0x6D,0x65,0x73,0x73,0x61,0x67,0x65};
HMAC_INFO   HmacInfo;

//--------------------------------------------------------------------
// Zero the HMAC_INFO structure and use the SHA1 algorithm for
// hashing.

ZeroMemory(&HmacInfo, sizeof(HmacInfo));
HmacInfo.HashAlgid = CALG_SHA1;

//--------------------------------------------------------------------
// Acquire a handle to the default RSA cryptographic service provider.

if (!CryptAcquireContext(
    &hProv,                   // handle of the CSP
    NULL,                     // key container name
    NULL,                     // CSP name
    PROV_RSA_FULL,            // provider type
    CRYPT_VERIFYCONTEXT))     // no key access is requested
{
   printf(" Error in AcquireContext 0x%08x \n",
          GetLastError());
   goto ErrorExit;
}

//--------------------------------------------------------------------
// Derive a symmetric key from a hash object by performing the
// following steps:
//    1. Call CryptCreateHash to retrieve a handle to a hash object.
//    2. Call CryptHashData to add a text string (password) to the 
//       hash object.
//    3. Call CryptDeriveKey to create the symmetric key from the
//       hashed password derived in step 2.
// You will use the key later to create an HMAC hash object. 

if (!CryptCreateHash(
    hProv,                    // handle of the CSP
    CALG_SHA1,                // hash algorithm to use
    0,                        // hash key
    0,                        // reserved
    &hHash))                  // address of hash object handle
{
   printf("Error in CryptCreateHash 0x%08x \n",
          GetLastError());
   goto ErrorExit;
}

if (!CryptHashData(
    hHash,                    // handle of the hash object
    Data1,                    // password to hash
    sizeof(Data1),            // number of bytes of data to add
    0))                       // flags
{
   printf("Error in CryptHashData 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

if (!CryptDeriveKey(
    hProv,                    // handle of the CSP
    CALG_RC4,                 // algorithm ID
    hHash,                    // handle to the hash object
    0,                        // flags
    &hKey))                   // address of the key handle
{
   printf("Error in CryptDeriveKey 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

//--------------------------------------------------------------------
// Create an HMAC by performing the following steps:
//    1. Call CryptCreateHash to create a hash object and retrieve 
//       a handle to it.
//    2. Call CryptSetHashParam to set the instance of the HMAC_INFO 
//       structure into the hash object.
//    3. Call CryptHashData to compute a hash of the message.
//    4. Call CryptGetHashParam to retrieve the size, in bytes, of
//       the hash.
//    5. Call malloc to allocate memory for the hash.
//    6. Call CryptGetHashParam again to retrieve the HMAC hash.

if (!CryptCreateHash(
    hProv,                    // handle of the CSP.
    CALG_HMAC,                // HMAC hash algorithm ID
    hKey,                     // key for the hash (see above)
    0,                        // reserved
    &hHmacHash))              // address of the hash handle
{
   printf("Error in CryptCreateHash 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

if (!CryptSetHashParam(
    hHmacHash,                // handle of the HMAC hash object
    HP_HMAC_INFO,             // setting an HMAC_INFO object
    (BYTE*)&HmacInfo,         // the HMAC_INFO object
    0))                       // reserved
{
   printf("Error in CryptSetHashParam 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

if (!CryptHashData(
    hHmacHash,                // handle of the HMAC hash object
    Data2,                    // message to hash
    sizeof(Data2),            // number of bytes of data to add
    0))                       // flags
{
   printf("Error in CryptHashData 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

//--------------------------------------------------------------------
// Call CryptGetHashParam twice. Call it the first time to retrieve
// the size, in bytes, of the hash. Allocate memory. Then call 
// CryptGetHashParam again to retrieve the hash value.

if (!CryptGetHashParam(
    hHmacHash,                // handle of the HMAC hash object
    HP_HASHVAL,               // query on the hash value
    NULL,                     // filled on second call
    &dwDataLen,               // length, in bytes, of the hash
    0))
{
   printf("Error in CryptGetHashParam 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

pbHash = (BYTE*)malloc(dwDataLen);
if(NULL == pbHash) 
{
   printf("unable to allocate memory\n");
   goto ErrorExit;
}
    
if (!CryptGetHashParam(
    hHmacHash,                 // handle of the HMAC hash object
    HP_HASHVAL,                // query on the hash value
    pbHash,                    // pointer to the HMAC hash value
    &dwDataLen,                // length, in bytes, of the hash
    0))
{
   printf("Error in CryptGetHashParam 0x%08x \n", GetLastError());
   goto ErrorExit;
}

// Print the hash to the console.

printf("The hash is:  ");
for(DWORD i = 0 ; i < dwDataLen ; i++) 
{
   printf("%2.2x ",pbHash[i]);
}
printf("\n");

// Free resources.
ErrorExit:
    if(hHmacHash)
        CryptDestroyHash(hHmacHash);
    if(hKey)
        CryptDestroyKey(hKey);
    if(hHash)
        CryptDestroyHash(hHash);    
    if(hProv)
        CryptReleaseContext(hProv, 0);
    if(pbHash)
        free(pbHash);
    return 0;
}

Solution

  • Use plaintext key, something like this:

    #include <stdio.h>
    #include <windows.h>
    #include <wincrypt.h>
    
    int main()
    {
        //--------------------------------------------------------------------
        // Declare variables.
        //
        // hProv:           Handle to a cryptographic service provider (CSP). 
        //                  This example retrieves the default provider for  
        //                  the PROV_RSA_FULL provider type.  
        // hHash:           Handle to the hash object needed to create a hash.
        // hKey:            Handle to a symmetric key. This example creates a 
        //                  key for the RC4 algorithm.
        // hHmacHash:       Handle to an HMAC hash.
        // pbHash:          Pointer to the hash.
        // dwDataLen:       Length, in bytes, of the hash.
        // Data1:           Password string used to create a symmetric key.
        // Data2:           Message string to be hashed.
        // HmacInfo:        Instance of an HMAC_INFO structure that contains 
        //                  information about the HMAC hash.
        // 
        HCRYPTPROV  hProv = NULL;
        HCRYPTKEY   hKey = NULL;
        HCRYPTHASH  hHmacHash = NULL;
        PBYTE       pbHash = NULL;
        DWORD       dwDataLen = 0;
        BYTE        Data1[] = { 0x70,0x61,0x73,0x73,0x77,0x6F,0x72,0x64 };
        BYTE        Data2[] = { 0x6D,0x65,0x73,0x73,0x61,0x67,0x65 };
        HMAC_INFO   HmacInfo;
    
        struct KEYBLOB
        {
            BLOBHEADER header;
            DWORD size;
        };
    
        struct KEYBLOB* keyblob = NULL;
    
        //--------------------------------------------------------------------
        // Zero the HMAC_INFO structure and use the SHA1 algorithm for
        // hashing.
    
        ZeroMemory(&HmacInfo, sizeof(HmacInfo));
        HmacInfo.HashAlgid = CALG_SHA1;
    
        //--------------------------------------------------------------------
        // Acquire a handle to the default RSA cryptographic service provider.
    
        if (!CryptAcquireContext(
            &hProv,                   // handle of the CSP
            NULL,                     // key container name
            NULL,                     // CSP name
            PROV_RSA_FULL,            // provider type
            CRYPT_VERIFYCONTEXT))     // no key access is requested
        {
            printf(" Error in AcquireContext 0x%08x \n",
                GetLastError());
            goto ErrorExit;
        }
    
        keyblob = (KEYBLOB*)malloc(sizeof(KEYBLOB) + sizeof(Data1));
        if (keyblob == NULL) {
            printf("Error malloc failed to allocate memory of %zu bytes\n",
                sizeof(KEYBLOB) + sizeof(Data1));
            goto ErrorExit;
        }
        keyblob->header.bType = PLAINTEXTKEYBLOB;
        keyblob->header.bVersion = CUR_BLOB_VERSION;
        keyblob->header.reserved = 0;
        keyblob->header.aiKeyAlg = CALG_RC2;
    
        keyblob->size = sizeof(Data1);
    
        memcpy((PBYTE)keyblob + sizeof(KEYBLOB), Data1, sizeof(Data1));
    
        if (!CryptImportKey(
            hProv,
            (PBYTE)keyblob,
            sizeof(KEYBLOB) + sizeof(Data1),
            0,
            CRYPT_IPSEC_HMAC_KEY,
            &hKey))
        {
            printf("Error in CryptImportKey 0x%08x \n",
                GetLastError());
            goto ErrorExit;
        }
    
        //--------------------------------------------------------------------
        // Create an HMAC by performing the following steps:
        //    1. Call CryptCreateHash to create a hash object and retrieve 
        //       a handle to it.
        //    2. Call CryptSetHashParam to set the instance of the HMAC_INFO 
        //       structure into the hash object.
        //    3. Call CryptHashData to compute a hash of the message.
        //    4. Call CryptGetHashParam to retrieve the size, in bytes, of
        //       the hash.
        //    5. Call malloc to allocate memory for the hash.
        //    6. Call CryptGetHashParam again to retrieve the HMAC hash.
    
        if (!CryptCreateHash(
            hProv,                    // handle of the CSP.
            CALG_HMAC,                // HMAC hash algorithm ID
            hKey,                     // key for the hash (see above)
            0,                        // reserved
            &hHmacHash))              // address of the hash handle
        {
            printf("Error in CryptCreateHash 0x%08x \n",
                GetLastError());
            goto ErrorExit;
        }
    
        if (!CryptSetHashParam(
            hHmacHash,                // handle of the HMAC hash object
            HP_HMAC_INFO,             // setting an HMAC_INFO object
            (BYTE*)&HmacInfo,         // the HMAC_INFO object
            0))                       // reserved
        {
            printf("Error in CryptSetHashParam 0x%08x \n",
                GetLastError());
            goto ErrorExit;
        }
    
        if (!CryptHashData(
            hHmacHash,                // handle of the HMAC hash object
            Data2,                    // message to hash
            sizeof(Data2),            // number of bytes of data to add
            0))                       // flags
        {
            printf("Error in CryptHashData 0x%08x \n",
                GetLastError());
            goto ErrorExit;
        }
    
        //--------------------------------------------------------------------
        // Call CryptGetHashParam twice. Call it the first time to retrieve
        // the size, in bytes, of the hash. Allocate memory. Then call 
        // CryptGetHashParam again to retrieve the hash value.
    
        if (!CryptGetHashParam(
            hHmacHash,                // handle of the HMAC hash object
            HP_HASHVAL,               // query on the hash value
            NULL,                     // filled on second call
            &dwDataLen,               // length, in bytes, of the hash
            0))
        {
            printf("Error in CryptGetHashParam 0x%08x \n",
                GetLastError());
            goto ErrorExit;
        }
    
        pbHash = (BYTE*)malloc(dwDataLen);
        if (NULL == pbHash)
        {
            printf("unable to allocate memory\n");
            goto ErrorExit;
        }
    
        if (!CryptGetHashParam(
            hHmacHash,                 // handle of the HMAC hash object
            HP_HASHVAL,                // query on the hash value
            pbHash,                    // pointer to the HMAC hash value
            &dwDataLen,                // length, in bytes, of the hash
            0))
        {
            printf("Error in CryptGetHashParam 0x%08x \n", GetLastError());
            goto ErrorExit;
        }
    
        // Print the hash to the console.
    
        printf("The hash is:  ");
        for (DWORD i = 0; i < dwDataLen; i++)
        {
            printf("%2.2x ", pbHash[i]);
        }
        printf("\n");
    
        // Free resources.
    ErrorExit:
        if (hHmacHash)
            CryptDestroyHash(hHmacHash);
        if (hKey)
            CryptDestroyKey(hKey);
        if (keyblob)
            free(keyblob);
        if (hProv)
            CryptReleaseContext(hProv, 0);
        if (pbHash)
            free(pbHash);
        return 0;
    }