You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Lemon Squeezy Webhook验证Python代码转C#遇签名匹配失败问题

Lemon Squeezy Webhook签名验证C#代码问题排查

问题背景

我正在对接支付提供商Lemon Squeezy的Webhook功能,需要验证请求确实来自该提供商,但官方文档仅提供PHP、Node.js和Python代码示例,而我的应用基于C#开发。

签名密钥用于生成请求payload的哈希值,并通过请求的X-Signature头传递。该密钥是我在提供商后台设置的,本地通过配置依赖注入获取,签名从请求头获取,请求体取自当前请求。目前我写出的C#代码计算出的digest始终与signature不匹配,需要排查问题并找到解决方案。

官方Python示例代码

import hashlib
import hmac

signature = request.META['HTTP_X_SIGNATURE']
secret = '[SIGNING_SECRET]'

digest = hmac.new(secret.encode(), request.body, hashlib.sha256).hexdigest()

if not hmac.compare_digest(digest, signature):
    raise Exception('Invalid signature.')

我编写的C#代码

var signature = Request.Headers["X-Signature"];
string secret = _configuration.GetValue<string>("Keys:LMWebhookSigningSecret");
string requestBody = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); 

using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
    byte[] computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(requestBody));
    string digest = BitConverter.ToString(computedHash).Replace("-", "").ToLower();

    if (!digest.Equals(signature))
    {
        throw new Exception("Invalid signature.");
    }
}

排查方向及解决方案

以下是可能导致签名不匹配的原因及对应修正方案:

  • 请求体流位置问题:如果请求体流被之前的中间件读取过,流的位置会停在末尾,导致StreamReader读取到空内容。解决方法是在读取前重置流位置:

    HttpContext.Request.Body.Position = 0; // 重置流到开头
    string requestBody = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
    
  • 签名头取值问题:Request.Headers["X-Signature"]返回的是StringValues类型,若存在多个头值会导致取值错误。应改为获取单个值:

    var signature = Request.Headers["X-Signature"].FirstOrDefault() ?? string.Empty;
    
  • 安全比对方法缺失:Python使用hmac.compare_digest防时序攻击,C#应使用对应的安全方法CryptographicOperations.FixedTimeEquals(需引用System.Security.Cryptography命名空间),同时要将字符串转为字节数组后比对:

    if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(digest), Encoding.UTF8.GetBytes(signature)))
    {
        throw new Exception("Invalid signature.");
    }
    
  • 编码一致性验证:确保请求体的编码为UTF-8,避免因编码差异导致哈希计算错误。Lemon Squeezy的Webhook默认使用UTF-8编码,若存在特殊字符可尝试直接读取字节流而非转成字符串:

    HttpContext.Request.Body.Position = 0;
    byte[] requestBodyBytes = new byte[HttpContext.Request.ContentLength.Value];
    await HttpContext.Request.Body.ReadAsync(requestBodyBytes, 0, requestBodyBytes.Length);
    
    using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
    {
        byte[] computedHash = hmac.ComputeHash(requestBodyBytes);
        string digest = BitConverter.ToString(computedHash).Replace("-", "").ToLower();
        // 后续比对逻辑...
    }
    

修正后的完整C#代码示例

using System.Security.Cryptography;
using System.Text;

var signature = Request.Headers["X-Signature"].FirstOrDefault() ?? string.Empty;
string secret = _configuration.GetValue<string>("Keys:LMWebhookSigningSecret");

// 重置请求体流位置并读取原始字节
HttpContext.Request.Body.Position = 0;
byte[] requestBodyBytes = new byte[HttpContext.Request.ContentLength.Value];
await HttpContext.Request.Body.ReadAsync(requestBodyBytes, 0, requestBodyBytes.Length);

using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
    byte[] computedHash = hmac.ComputeHash(requestBodyBytes);
    string digest = BitConverter.ToString(computedHash).Replace("-", "").ToLower();

    if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(digest), Encoding.UTF8.GetBytes(signature)))
    {
        throw new Exception("Invalid signature.");
    }
}

内容的提问来源于stack exchange,提问作者nerdalert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:06:32