Reputation: 31
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?
Upvotes: 2
Views: 4429
Reputation: 1
$hmac = $_GET['hmac'];
$str = http_build_query([
'host' => $_GET['host'],
'shop' => $_GET['shop'],
'timestamp' => $_GET['timestamp']
]);
$ver_hmac = hash_hmac('sha256',$str,'api_secret', false);
return $ver_hmac === $hmac;
It requires these 3 parameters to verify the hmac - (host,shop,timestamp)
Upvotes: 0
Reputation: 3778
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);
}
Upvotes: 0
Reputation: 1461
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.
Upvotes: 0
Reputation: 1337
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';
}
}
?>
Upvotes: 1
Reputation: 101
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);
Upvotes: 2