I'm trying to publish an app on Shopify marketplace by following this documentation. And I'm stuck on step-3 of the oauth documentation wherein you have to do 'HMAC Signature Validation'.
Documentation states that you have to process the string (specified below) through HMAC-SHA256 using app's shared secret key.
String = "shop=some-shop.myshopify.com×tamp=1337178173"
I'm trying to implement the steps using Java. Following is gist of the code that I have used.
private static final String HMAC_ALGORITHM = "HmacSHA256";
String key = "hush";
String data = "shop=some-shop.myshopify.com×tamp=1337178173";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(),HMAC_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(keySpec);
byte[] rawHmac = mac.doFinal(data.getBytes());
System.out.println(Hex.encodeHexString(rawHmac));
The code produces the following string:
c2812f39f84c32c2edaded339a1388abc9829babf351b684ab797f04cd94d4c7
Through some random search on Shopify developer forum I found the link to a question.
The last message from @Shayne suggests that we have to make changes in data
variable by adding protocol
field.
But it didn't work out :(
Can anyone tell me what should be done?Do I have to make modifications in my code or the process in the documentation have changed.
Please help.
The example is wrong apparently. Your hash code is OK. You'll need to make sure you include all parameters from the Shopify response e.g. the input for verification of a response would look like:
code={code}&protocol=https://&store={store}×tamp={timestamp}
See: https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/you-broke-my-build-hmac-verification-broken-282951
Here's the java code you need to verify Shopify HMAC. The protocol parameter isn't required unless it was in the result from shopify, which it wasn't from me.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String HMAC_ALGORITHM = "HmacSHA256";
resp.setContentType("text/html;charset=UTF-8");
Map<String,String[]> parameters = req.getParameterMap();
String data = null;
SortedSet<String> keys = new TreeSet<String>(parameters.keySet());
for (String key : keys) {
if (!key.equals("hmac")&&!key.equals("signature")){
if (data == null){
data = key + "=" +req.getParameter(key);
}
else {
data = data + "&" + key + "=" + req.getParameter(key);
}
}
}
SecretKeySpec keySpec = new SecretKeySpec(SHARED_KEY.getBytes(),HMAC_ALGORITHM);
Mac mac = null;
try {
mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(keySpec);
byte[] rawHmac = mac.doFinal(data.getBytes());
if (Hex.encodeHexString(rawHmac).equals(req.getParameter("hmac"))){
//THE HMAC IS VERIFIED
} else {
//THE HMAC IS NOT VERIFIED
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
}
Interestingly, the timestamp parameter in data turns into
×tamp=1459537704
instead of
×tamp=1459537704
here is my prod code:
public class HMACValidator {
public static String sha256HMAC(String key, String data) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, DecoderException {
Mac hmac = Mac.getInstance("HmacSHA256");
System.out.println("data "+data);
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
hmac.init(secret_key);
return Hex.encodeHexString(hmac.doFinal(data.getBytes("UTF-8")));
}
public static boolean validateShopifyAskForPermission(String key, String hmac, String shop, String timestamp) throws Exception {
return (sha256HMAC(key, "shop="+shop+"×tamp="+timestamp).compareTo(hmac) == 0);
}
}