1
votes

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&timestamp=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&timestamp=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.

3

3 Answers

2
votes

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

&timestamp=1459537704
1
votes

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}&timestamp={timestamp}

See: https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/you-broke-my-build-hmac-verification-broken-282951

0
votes

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+"&timestamp="+timestamp).compareTo(hmac) == 0);
    }
}