Search code examples
javascriptphputf-8hmaccryptojs

PHP and CryptoJS output does not match for several functions


We need to call third party api from php laravel backend. To call that api we need to send some data in signed form for authorization purpose. For signing process the have provided the following abstract method

signature = base64(hmacSHA256(utf8EncodingOf(partnerSecret), utf8EncodingOf(stringToSign)))

They also have provided a postman collection to test the api. While investigating the collection we stumbled upon the "Pre-request Script" that was doing the signature generation with CryptoJS library.

The main problem arises with the start of signature process, until then all the outputs are same.

Consider the below JS code with CryptoJS.

    let stringToSign = 'aReallyLongStringNeededToBeSigned';
    let secret = 'thisIsVerySecret';

    stringToSign = CryptoJS.enc.Utf8.parse(stringToSign);
    let key = CryptoJS.enc.Utf8.parse(secret);
    console.log('after encode stringToSign => '+stringToSign);
    console.log('after encode key => '+key);

    let hash = CryptoJS.HmacSHA256(stringToSign, key);
    let signature = ''+CryptoJS.enc.Base64.stringify(hash);
    console.log('generated hash => '+hash);
    console.log('generated signature => '+signature);

Which give the following output:

after encode stringToSign => 615265616c6c794c6f6e67537472696e674e6565646564546f42655369676e6564
after encode key => 74686973497356657279536563726574
generated hash => 703e481c7e9a0409a0a78853679e15d34f7ce8972b8d9678471cd0d98f4e61cf
generated signature => cD5IHH6aBAmgp4hTZ54V00986JcrjZZ4RxzQ2Y9OYc8=

Now consider the following PHP code for similar work:

        $stringToSign = 'aReallyLongStringNeededToBeSigned';
        $secret = 'thisIsVerySecret';

        $stringToSign = utf8_encode($stringToSign);
        $key = utf8_encode($secret);
        Log::info('after encode stringToSign => '.$stringToSign);
        Log::info('after encode key => '.$key);

        $hash = hash_hmac('sha256', $stringToSign, $key, true);
        $signature = base64_encode($hash);
        Log::info('generated hash => '.$hash);
        Log::info('generated signature => '.$signature);

In PHP it gives the following output.

after encode stringToSign => aReallyLongStringNeededToBeSigned  
after encode key => thisIsVerySecret  
generated hash => p>H~�   ���Sg��O|�+��xG�ُNa�  
generated signature => cD5IHH6aBAmgp4hTZ54V00986JcrjZZ4RxzQ2Y9OYc8= 

The outputs after different. And it starts with the utf8 encoding method. We have also tried to see hmacSha256 of php with the encoded output generated by CryptoJS as follows.

$hash = hash_hmac('sha256', "615265616c6c794c6f6e67537472696e674e6565646564546f42655369676e6564",
            "74686973497356657279536563726574", true);

With the last value tweaking between true and false the output is as below.

generated hash => (,F\=�V�կ��w}��@��j6�z�VP�S  // true
generated hash => 281e2c465c3dbd56b1d5af84ea777d92e34095e16a0f360ee87a7fa156509f53 // false

We surely are missing something here. So the questions are:

  1. Why the output of the same hashing algorithms is different(specially hmacSha256)?
  2. Also what are the functions for PHP to get similar results as CryptoJS?

Please share your valuable findings. Thanks in advance.....

N.B.: We have already seen the followings. But those doesn't help our situation to get desired output.

  1. match PHP and CryptoJS output
  2. Why HMAC sha256 return different value on PHP & Javascript

Solution

  • What character encoding did you store your PHP script in? If that is UTF-8 already, then you should not apply utf8_encode to your input values, because that would “double” the encoding then.

    If your script is saved in UTF-8 encoding already, then

    base64_encode(hash_hmac('sha256', 'aReallyLongStringNeededToBeSigned', 'thisIsVerySecret', true));
    

    gets you cD5IHH6aBAmgp4hTZ54V00986JcrjZZ4RxzQ2Y9OYc8=, and that is exactly the result you want here.