4

我已经设置了一个端点来接收来自 Shopify 的 webhook 请求。

Shopify 发出的请求包括一个 HMAC 标头,该标头是从共享密钥和请求正文创建的。

我需要计算我服务器上的 HMAC 并将其与请求标头中的值相匹配,以确保请求是真实的。

我似乎无法在 .NET 中创建适当的机制来创建匹配的 HMAC 值。

我此时的算法如下:

public static string CreateHash(string data)
    {
        string sharedSecretKey = "MY_KEY";

        byte[] keyBytes = Encoding.UTF8.GetBytes(sharedSecretKey);
        byte[] dataBytes = Encoding.UTF8.GetBytes(data);

        //use the SHA256Managed Class to compute the hash
        System.Security.Cryptography.HMACSHA256 hmac = new HMACSHA256(keyBytes);
        byte[] hmacBytes = hmac.ComputeHash(dataBytes);

        //retun as base64 string. Compared with the signature passed in the header of the post request from Shopify. If they match, the call is verified.
        return System.Convert.ToBase64String(hmacBytes);
    }

可以在此处找到用于验证其 webhook 的 Shopify 文档,但仅包含 PHP 和 Ruby 示例。

谁能看到我可能做错了什么?我是否应该将整个 JSON 请求正文作为字符串传递到此方法中?

4

4 回答 4

7
    private static bool Validate(string sharedSecretKey)
    {
        var data = GetStreamAsText(HttpContext.Current.Request.InputStream, HttpContext.Current.Request.ContentEncoding);
        var keyBytes = Encoding.UTF8.GetBytes(sharedSecretKey);
        var dataBytes = Encoding.UTF8.GetBytes(data);

        //use the SHA256Managed Class to compute the hash
        var hmac = new HMACSHA256(keyBytes);
        var hmacBytes = hmac.ComputeHash(dataBytes);

        //retun as base64 string. Compared with the signature passed in the header of the post request from Shopify. If they match, the call is verified.
        var hmacHeader = HttpContext.Current.Request.Headers["x-shopify-hmac-sha256"];
        var createSignature = Convert.ToBase64String(hmacBytes);
        return hmacHeader == createSignature;
    }

    private static string GetStreamAsText(Stream stream, Encoding encoding)
    {
        var bytesToGet = stream.Length;
        var input = new byte[bytesToGet];
        stream.Read(input, 0, (int)bytesToGet);
        stream.Seek(0, SeekOrigin.Begin); // reset stream so that normal ASP.NET processing can read data
        var text = encoding.GetString(input);
        return text;
    }
于 2014-02-14T02:55:18.567 回答
2

正如您在问题中提到的那样,您应该在您的方法中散列整个 json 请求正文。

我的 .NET 不太好,但下面是 ruby​​ 示例的一部分,它向您展示了该怎么做:

post '/' do

  . . .

  data = request.body.read
  verified = verify_webhook(data, env["HTTP_X_SHOPIFY_HMAC_SHA256"])

  . . .

end

您可以看到我们只是获取请求的主体(作为字符串)并将其逐字逐句地投入到验证方法中。试一试,希望你有更多的运气。

于 2012-11-08T14:08:01.437 回答
2

作为对上述代码的改进,您可以将其转换为属性,只需进行一些小改动:

public class VerifyShopifyAttribute : ActionFilterAttribute
{
    private readonly string sharedSecret = "abc";

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!ValidateHash(actionContext))
        {
            // reject the request with a 400 error
            var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad Request");
            actionContext.Response = response;
        }
    }

    private bool ValidateHash(HttpActionContext actionContext)
    {
        var context = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];
        context.Request.InputStream.Seek(0, SeekOrigin.Begin);

        using (var stream = new MemoryStream())
        {
            context.Request.InputStream.CopyTo(stream);
            string requestBody = Encoding.UTF8.GetString(stream.ToArray());

            var keyBytes = Encoding.UTF8.GetBytes(sharedSecret);
            var dataBytes = Encoding.UTF8.GetBytes(requestBody);

            //use the SHA256Managed Class to compute the hash
            var hmac = new HMACSHA256(keyBytes);
            var hmacBytes = hmac.ComputeHash(dataBytes);

            //retun as base64 string. Compared with the signature passed in the header of the post request from Shopify. If they match, the call is verified.
            var hmacHeader = HttpContext.Current.Request.Headers["x-shopify-hmac-sha256"];
            var createSignature = Convert.ToBase64String(hmacBytes);
            return hmacHeader == createSignature;
        }
    }
}

然后你可以像这样对所有的 webhook 使用它:

[RoutePrefix("api")]
public class ShopifyWebHookController : ApiController
{
    [VerifyShopify]
    [HttpPost]
    public IHttpActionResult HandleWebhook(...)
    {
        ...
    }
}
于 2019-02-06T01:13:34.623 回答
0

我在使用 .NET Core 3.1 上的 C# 的 Azure Functions 3.0 框架中遇到了这个问题。受上述答案和我自己的修补的启发,我能够使用以下代码完成此操作。

// replace the appsettings variable with however you get your pre-shared signature
// req is passed in after being injected through the Azure Functions framework
private static async Task<bool> ValidateShopifySignature(HttpRequest req, AppSettings appSettings)
        {
            var hmacHeader = req.Headers["X-Shopify-Hmac-Sha256"];
           
            var sharedSignatureBytes = Encoding.UTF8.GetBytes(appSettings.ShopifyWebhookSignature);
            using var hmac = new HMACSHA256(sharedSignatureBytes);

            //copy the request body to a memory stream then convert it to a byte[]
            using MemoryStream dataStream = new();
            await req.Body.CopyToAsync(dataStream);
            var dataBytes = dataStream.ToArray();
            
            //compute a hash of the body based on the signature 
            var generatedHmacHashBytes = hmac.ComputeHash(dataBytes);
            var generatedSignature = Convert.ToBase64String(generatedHmacHashBytes);
          
            //compare that signature to the one that Shopify generated and sent over
            return hmacHeader == generatedSignature;
        }
于 2021-04-28T00:02:51.867 回答