简体   繁体   中英

shopify hmac verification php

This is my code :

function verifyRequest($request, $secret) {
  // Per the Shopify docs:
  // Everything except hmac and signature...

  $hmac = $request['hmac'];
  unset($request['hmac']);
  unset($request['signature']);

  // Sorted lexilogically...
  ksort($request);

  // Special characters replaced...
  foreach ($request as $k => $val) {
    $k = str_replace('%', '%25', $k);
    $k = str_replace('&', '%26', $k);
    $k = str_replace('=', '%3D', $k);
    $val = str_replace('%', '%25', $val);
    $val = str_replace('&', '%26', $val);
    $params[$k] = $val;
  }

  echo $http = "protocol=". urldecode("https://").http_build_query( $params) ;
  echo $test = hash_hmac("sha256", $http , $secret);

  // enter code hereVerified when equal
  return $hmac === $test;
}

The hmac from shopi and hmac created from my code is not matching.

What am I doing wrong?

You only need to include the request parameters when creating the list of key-value pairs - don't need "protocol=https://".

https://help.shopify.com/api/getting-started/authentication/oauth#verification

You'll need to urldecode() the result of http_build_query(). It returns a url-encoded query string.

http://php.net/manual/en/function.http-build-query.php

Instead of:

 echo $http = "protocol=". urldecode("https://").http_build_query( $params) ;
 echo $test = hash_hmac("sha256", $http , $secret);

Something like this:

 $http = urldecode(http_build_query($params));
 $test = hash_hmac('sha256', $http, $secret);

hmac can be calculated in any programming language using sha256 cryptographic algorithm.

However the doc for hmac verification is provided by shopify but still there is confusion among app developers how to implement it correctly.

Here is the code in php for hmac verification. Ref.http://code.codify.club

<?php

function verifyHmac()
{
  $ar= [];
  $hmac = $_GET['hmac'];
  unset($_GET['hmac']);

  foreach($_GET as $key=>$value){

    $key=str_replace("%","%25",$key);
    $key=str_replace("&","%26",$key);
    $key=str_replace("=","%3D",$key);
    $value=str_replace("%","%25",$value);
    $value=str_replace("&","%26",$value);

    $ar[] = $key."=".$value;
  }

  $str = join('&',$ar);
  $ver_hmac =  hash_hmac('sha256',$str,"YOUR-APP-SECRET-KEY",false);

  if($ver_hmac==$hmac)
  {
    echo 'hmac verified';
  }

}
?>

Notice for other requests like the App Proxy a HMAC will not be preset, so you'll need to calculate the signature. Here a function that caters for both types of requests including webhooks:

public function authorize(Request $request)
{
    if( isset($request['hmac']) || isset($request['signature']) ){
        try {
            $signature = $request->except(['hmac', 'signature']);

            ksort($signature);

            foreach ($signature as $k => $val) {
                $k = str_replace('%', '%25', $k);
                $k = str_replace('&', '%26', $k);
                $k = str_replace('=', '%3D', $k);
                $val = str_replace('%', '%25', $val);
                $val = str_replace('&', '%26', $val);
                $signature[$k] = $val;
            }

            if(isset($request['hmac'])){
                $test = hash_hmac('sha256', http_build_query($signature), env('SHOPIFY_API_SECRET'));

                if($request->input('hmac') === $test){
                    return true;
                }
            } elseif(isset($request['signature'])){
                $test = hash_hmac('sha256', str_replace('&', '', urldecode(http_build_query($signature))), env('SHOPIFY_API_SECRET'));

                if($request->input('signature') === $test){
                    return true;
                }
            }
        } catch (Exception $e) {
            Bugsnag::notifyException($e);
        }
    } else { // If webhook
        $calculated_hmac = base64_encode(hash_hmac('sha256', $request->getContent(), env('SHOPIFY_API_SECRET'), true));

        return hash_equals($request->server('HTTP_X_SHOPIFY_HMAC_SHA256'), $calculated_hmac);
    }

    return false;
}

The above example uses some Laravel functions, so уоu may want to replace them if you use a different framework.

I've got the following to do this:

// Remove the 'hmac' parameter from the query string
$query_string_rebuilt = removeParamFromQueryString($_SERVER['QUERY_STRING'], 'hmac');
// Check the HMAC
if(!checkHMAC($_GET['hmac'], $query_string_rebuilt, $shopify_api_secret_key)) {
    // Error code here
}

/**
 * @param string $comparison_data
 * @param string $data
 * @param string $key
 * @param string $algorithm
 * @param bool $binary
 * @return bool
 */
function checkHMAC($comparison_data, $data, $key, $algorithm = 'sha256', $binary=false) {
    // Check the HMAC
    $hash_hmac = hash_hmac($algorithm, $data, $key, $binary);
    // Return true if there's a match
    if($hash_hmac === $comparison_data) {
        return true;
    }
    return false;
}

/**
 * @param string $query_string
 * @param string $param_to_remove
 * @return string
 */
function removeParamFromQueryString(string $query_string, string $param_to_remove) {
    parse_str($query_string, $query_string_into_array);
    unset($query_string_into_array[$param_to_remove]);
    return http_build_query($query_string_into_array);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM