Click here to Skip to main content
14,971,897 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hello,

I wrote a program in C with the Win32 API that could, or was supposed to, be able to generate correct hashes for an input message. The algorithms to be used were all one-way, such as MD2, MD4, MD5, and SHA-1. These are the ones I tested, because apparently those are the ones Win32 support.

Then I wrote a PHP script to do much the same as I wanted to test the validity of the hashes based on the message I wanted hashed. When my script returned different hashes I decided to download a C program from MSDN (which I will also provide you with) to fix any possible mistakes I may have made.

The PHP script can be a single line:

Or you could use the hash() function like I did in my script: hash('message', 'md5')

I decided to combine it with an HTML form to make life easier, and I'm happy to share it with you if it helps. Both the PHP/HTML and C/Win32 are in "What I have tried", or you can download the latter here: Example C Program: Creating an HMAC - Win32 apps | Microsoft Docs[^]

I hope you can help me alter the C code to give the same results as the PHP functions, or at least help me understand why they don't. Thank you very much!

What I have tried:

PHP
<?php

error_reporting(0);
extract($_POST);

if (isset($data)) {
    if ($algo == 'other') $algo = $other;

    $hash = @hash($algo, $data);
    if (is_bool($hash)) { // failure, should be string
        $hash = 'Fejl: "' . $other . '" er ikke en understøttet algoritme.';
    }
}

?>

<form action="" method="post">
<b>Algorithm</b><br>
<input required type="radio" name="algo" value="md5" <?=$algo == 'md5' ? 'checked' : '';?>><span>MD5</span>
<input required type="radio" name="algo" value="sha1"<?=$algo == 'sha1' ? 'checked' : '';?>><span>SHA-1</span>
<input required type="radio" name="algo" value="sha256"<?=$algo == 'sha256' ? 'checked' : '';?>><span>SHA-2</span>
<input onclick="_focus('txt_oth');" required type="radio" name="algo" value="other" id="rad_oth" <?=$algo == $other ? 'checked' : '';?>><span>Other:&nbsp;</span>
<input onfocus="_check('rad_oth');" type="text" name="other" size="5" id="txt_oth" value="<?=$other;?>">

<br><br>

<textarea style="width: 100%; height: 100px;" name="data"><?=$data;?></textarea>

<br><br>
<input type="submit" value="Show hash">

<?php

if (isset($hash)) {
    echo "<span>&nbsp;{$hash}</span>";
}

?>

</form>

<script>
function _focus(id) { document.getElementById(id).focus(); }
function _check(id) { document.getElementById(id).checked = true; }
</script>


And the C program I promised:
C++
#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;
}
Posted
Updated 29-Mar-21 4:46am

1 solution

I'm no expert, but your C code seems to be generating an HMAC[^], whereas your PHP code is just generating a raw un-keyed hash.

Your PHP code would need to use hash_hmac[^] with the same secret key to generate the same result.
   
Comments
deXo-fan 30-Mar-21 2:47am
   
I definitely don't think you're wrong, but it still doesn't generate the same as the PHP script.

In the PHP script I tried this:
hash_hmac($algo, $data, 'hej')

$algo being sha1, $data being 'Thomas' and 'hej' should be the secret key, right? Or is that where I'm wrong? I changed the C code here:

BYTE Data1[] = { 'h','e','j' };

I also tried outputting the integer value that landed in hKey and using that as the key in hash_hmac, but still different results.

Another question occurred to me: with just the win32 API, perhaps the part we're using now, is it possible to generate unkeyed hashes like PHP's sha1 and md5 functions do? I tried changing some of the constants given to the C functions, specifially CALG_HMAC on line 115 which expands to ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HMAC. First I tried removing ALG_SID_HMAC and then I tried replacing it with some other ALG_SID_* constants, both times getting an error.
Richard Deeming 30-Mar-21 3:27am
   
Have you seen this article?
Hashing using the Win32 Crypto API[^]
deXo-fan 30-Mar-21 14:26pm
   
I had not seen that article, no, but I have now and I downloaded the project.
After fiddling around with some of the source files for a while I finally managed to get the same result! So thank you VERY much, Richard, you've been a real friend!

I had to find a way to convert the hash from bytes presented as raw chars to a string consisting of the same bytes/chars but each one presented as its hexadecimal value. But even before I did that I could tell it gave the same result as PHP, only it looked different because my browser use a different symbol for unrecognized/unprintable char values than tbe dialog in that project, evidently. But the chars which could be printed both places were identical, and that confirmed the hashes had to be identical too.

I hope you (and other people who happen to stop by) read this question I'm about to ask and feel like answering: Do you still use the Win32 API for big programs nowadays when there are alternatives like .NETs WinForms which are a 1000 times faster in the sense that it doesn't require nearly as much code/effort from you?

Just consider this: if you wish to create a form with a menu, accelerators, and icons, then it will take a fraction of the time in .NET compared to C(++) with Win32. Same with almost all, if not all other custom controls (buttons, richedits, etc.). Especially the WebBrowser control!

And with WPF and .NET you can make a video player in a matter of seconds... Imagine doing the same thing in C++. Are huge Win32-pure applications a dying breed?

Hope to hear some peoples' thoughts on that. Anyway, thanks again Richard Deeming! You really impressed me here!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900