2
votes

We have a legacy application that creates a Salt and Hash using the CryptoHashData function of the Windows CryptoAPI. We take a random byte string, apply the hash and then apply that hash to a second string of data using CryptoHashData again. Here is the code. A random key is hashed and then hashed again with the secret data.

BYTE baKeyRandom[10] = {87,253, ...}; 
pszSecret = 'ABCDEF-G...';
::CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hSaveHash); 
::CryptHashData(hSaveHash, baKeyRandom, (DWORD)sizeof(baKeyRandom), 0);
::CryptHashData(hSaveHash, (LPBYTE)T2CW(pszSecret), (DWORD)_tcslen(pszSecret) * sizeof(WCHAR), 0);

We have been able to recreate the first step on PHP by using the simple md5() function to hash the random byte string baKeyRandom. The value returned by this hash matches exactly the first hash on the ::CryptoHashData.

$random = md5($random); 

matches exactly with

::CryptHashData(hSaveHash, baKeyRandom, (DWORD)sizeof(baKeyRandom), 0);

What we haven’t been able to figure out is how the CryptoAPI combines the first hash in the buffer with the second data string to being hashed with it.
i.e. How do we recreate the second step of

::CryptHashData(hSaveHash, (LPBYTE)T2CW(pszSecret), (DWORD)_tcslen(pszSecret) * sizeof(WCHAR), 0);

We’ve tried the most obvious methods of:

$random = md5($random); followed by any of the below
$key = md5($random.$secret); 
$key = md5($secret.$random); 
$key = md5($random.md5($secret)); 
$key = md5(md5($secret).$random);

None of these methods match the values from CryptoHashData(random) + CryptoHashData(secret)

Does anyone know how the CryptoHashData will combine the two values in the buffer that will then be hashed? Has anyone recreated this CryptoAPI hash+salt method on other platforms that could be reproduced in PHP? Yes, we know this isn’t secure and its not being used for passwords or PII. We would like to move away from this old and insecure method, but we need take this interim step of translating the original encryption to PHP first for interoperability with the existing deployed systems. Any help or guidance would be appreciated.

Here is the final working code based on accepted answer below.

<?php
function main(){
    $random = pack('c*', 22,194,253,.......); 
    $secret = pack('c*', 22,194,253,.......);

    $hash = hash_init("md5");
    hash_update($hash, $random);
    hash_update($hash, $secret);
    print(hash_final($hash));
}

main();
?>
1
First of all, this will help you understand how CryptHashData operates. See also the API cryptgethashparam - kelalaka
$key = md5($random.$secret); this must be work, however, you have to deal with the string conversions. You can also test this with two arrays before going to deal with the string conversions. - kelalaka
@kelalaka, Thanks for your reply. I has read the previous post that you referred to and as you can see I have tried the method combinations that were described there. Its unclear what happens when the second hash is combined with the buffer containing the first. It says its appended but I think there is more to it than that. I'm also using the CryptoGetHashParam function of the Crypto API referred to in your document link, thats how I can tell the result of the second hash is not matching the PHP results. PHP seems to treat the salt in a different way to the CryptoAPI. - Cosworth66
Unless I'm confused by what you are saying the test you describe is exactly what I'm doing and the results dont match. Be aware I cant change any method on the Windows side as that code cant be changed although I have created a test version. md5 random + md5($random.$secret) doesnt match CryptoHashData random + (CryptoHashData random + secret) The array and secret values are fixed so the difference is the way they're handled on each side which is what I trying to figure out. - Cosworth66
Hi @kelalaka, I've now tried the simple test that you describe and monitored the hash result's in 2 stages with CryptoGetHashParam. The first hash with CryptoHashData random1 matches exactly md5($random1). The second CryptoHashData random1 + random2 doesnt match any combination of $key = md5($random.$secret); $key = md5($secret.$random); $key = md5($random.md5($secret)); $key = md5(md5($secret).$random); Both sides use the same Byte array for the first hash and I just duplicated it as the input for the second hash to keep it very simple. Hash 1 matches, hash2 doesnt. - Cosworth66

1 Answers

3
votes

You cannot use md5() function in PHP for progressive updating of hash. Chaining of hash algorithms are based on internal state of algorithm, not inputs. The internal state of md5 cannot be transferred when we use md5() method, so progressive update is not possible.

You need to use hash_init(), hash_update(), and hash_final() for stream hashing. Here is an example:

<?php
$hash = hash_init("md5");
$data1="ABCD";
hash_update($hash, $data1);
$data2="ABCD";
hash_update($hash, $data2);
print(hash_final($hash));
?>

This code result will be similar to your Windows API calls.