SHA256 webhook signature from WooCommerce never ve

2019-02-14 16:34发布

I am receiving webhooks from a woocommerce site into a nodejs/express application. I am trying to verify the webhook's signature to prove authenticity, yet the hash I compute never corresponds to the signature that woocommerce reports in the hook's signature header.

Here is the code I am using to verify the authenticity:

function verifySignature(signature, payload, key){     
    var computedSignature = crypto.createHmac("sha256", key).update(payload).digest('base64');
    debug('computed signature: %s', computedSignature);
    return computedSignature === signature;
  }

This function is being called with the following parameters:

var signature = req.headers['x-wc-webhook-signature'];
verifySignature(signature, JSON.stringify(req.body), config.wooCommence.accounts.api[config.env].webhookSecret)

The webhook's signature headers reports the signature as BewIV/zZMbmuJkHaUwaQxjX8yR6jRktPZQN9j2+67Oo=. The result of the above operation, however, is S34YqftH1R8F4uH4Ya2BSM1rn0H9NiqEA2Nr7W1CWZs=

I have manually configured the secret on the webhook, and as you see in the code above, this same secret is also hardcoded in the express application. So either I am taking the wrong payload to compute the signature, or there is something else fishy that prevents me from verifying these signature.

Would appreciate any pointers to help me solve this issue.

4条回答
我欲成王,谁敢阻挡
2楼-- · 2019-02-14 16:51

Old question but maybe it helps some poor soul out there. The signature needs to be checked against the body and not the JSON it contains. i.e. the raw bytes of the body.

pseudo:

        byte[] body = request.Body;
        string signature = request.Header["X-WC-Webhook-Signature"];

        byte[] secretUtf8 = GetUtf8Bytes("yoursecrethere");
        byte[] hash = HMAC_SHA256.ComputeHash(body, secretUtf8);
        string hashBase64 = ToBase64String(hash);

        bool isValid = hashBase64 == signature;
查看更多
▲ chillily
3楼-- · 2019-02-14 17:02

Hash must be calculated over the 'raw body'. When used in an 'express application' and using JSON bodyParser middleware 'raw body' is lost, see How to access the raw body of the request before bodyparser? to hold-on to the 'raw body'.

For example:

// 'misuse' verify option  
app.use(bodyParser.json({
  verify: function(req,res,buf) { 
    req.rawBody=buf; 
  }
}));

var wcSignature = req.get('X-Wc-Webhook-Signature');
debug('wc signature: %s', wcSignature);
var calculatedSignature = crypto.createHmac('SHA256', secret)
  .update(req.rawBody, 'utf8')
  .digest('base64');
debug('calculated signature: %s', calculatedSignature);
查看更多
淡お忘
4楼-- · 2019-02-14 17:03

I stumbled upon this while searching for a solution to have an Asp.NET application check signature of the Woocommerce web hook. My answer is based on the pseudo code Johannes provided which worked great. I implemented a custom controller attribute to intercept the request and check the signature before it hits the API controller method:

public class HmacSignatureFilter : ActionFilterAttribute
{

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var requestContent = actionContext.Request.Content;
        var jsonContent = requestContent.ReadAsStringAsync().Result;
        var byteContent = requestContent.ReadAsByteArrayAsync().Result;

        //if the request contains this, it's the verification request from Woocommerce
        //when the webhook is created so let it pass through so it can be verified
        if (!jsonContent.Contains("webhook_id"))
        {
            var requestSignature = actionContext.Request.Headers;

            var bodyHash = HashHMAC("test", byteContent); //this is the shared key between Woo and custom API.  should be from config or database table.

            var signature = actionContext.Request.Headers.GetValues("x-wc-webhook-signature");

            if (bodyHash != signature.FirstOrDefault())
            {
                throw new HttpResponseException(HttpStatusCode.Forbidden);
            }
        }

        base.OnActionExecuting(actionContext);
    }


    private static string HashHMAC(string key, byte[] message)
    {
        var keyBytes = Encoding.UTF8.GetBytes(key);
        var hash = new HMACSHA256(keyBytes);

        var computedHash = hash.ComputeHash(message);
        return Convert.ToBase64String(computedHash);
    }
}

Then to use the filter in your Api controller:

[RoutePrefix("api/woo")]
public class WooController : ApiController
{

    public SomeService _service;

    public WooController()
    {
        this._service = new SomeService();
    }

    // POST api/values
    [Route("orderCreated")]
    [HttpPost]
    [HmacSignatureFilter]
    public string Post()
    {
        var requestContent = Request.Content;
        var jsonContent = requestContent.ReadAsStringAsync().Result;

        //this is the test request from Woocommerce.  Don't do anything but 
        //respond so it can verify the endpoint
        if (jsonContent.Contains("webhook_id"))
        {
            return "Webhook Test Success";
        }

        var wooOrder = JsonConvert.DeserializeObject<WooOrderModel>(jsonContent);

        //call a service to use the order data provided by WooCommerce
        _service.AddOrder(wooOrder);

        return "Success";
    }

}

Note: Hashing code was referenced from this SO post.

查看更多
冷血范
5楼-- · 2019-02-14 17:04

For people using node, this should do the trick.

var processWebHookSignature = function (secret, body, signature) {
  signatureComputed = crypto.createHmac('SHA256', secret).update(
    new Buffer(JSON.stringify(body), 'utf8')).digest('base64');

  return ( signatureComputed === signature ) ? true : false;
}
查看更多
登录 后发表回答