【问题标题】:Unable to get Typeform Webhook Signature with C# to work无法使用 C# 使 Typeform Webhook 签名正常工作
【发布时间】:2020-04-16 11:14:37
【问题描述】:

首先,这个问题已经被问到并回答了here,但它是特定于 Ruby/PHP 的,虽然我试图遵循它和 Typeform 本身的指导,但我无法在 C# 中实现 Typeform-Signature Check .

我已经编写了一个扩展方法来验证 Typeform 签名与通过 webhook 发送的有效负载。如果签名有效,则返回字符串 (json) 有效负载,否则返回错误。

public static class HttpRequestExtensions {
    private const string SignatureHeader = "Typeform-Signature";
    private static readonly Encoding encoding = new UTF8Encoding ();

    public static async Task<Result<string>> ValidateAndRetrievePayload (this HttpRequestMessage request, string key) {
        var headerValue = request.GetHeaderValue (SignatureHeader);
        if (string.IsNullOrWhiteSpace (headerValue)) return Result.Failure<string> ($"'{SignatureHeader}' Header not found or empty.");

        var json = await request.Content.ReadAsStringAsync ();
        var payload = encoding.GetBytes (json);
        using (var hmac256 = new HMACSHA256 (encoding.GetBytes (key))) {
            var hashPayload = hmac256.ComputeHash (payload);
            var base64String = Convert.ToBase64String (hashPayload);
            var hashResult = $"sha256={base64String}";
            if (hashResult.Equals (headerValue)) return Result.Success (json);
            return Result.Failure<string> ($"'{SignatureHeader}' does not match. Header: `{headerValue}` | Hash: `{hashResult}`");
        }
    }
}

基于在 SO 上找到的其他问题,我修改了方法以在不编码的情况下运行(见下文),但结果仍然相同,哈希不匹配。

public static class HttpRequestExtensions
{
    private const string SignatureHeader = "Typeform-Signature";

    public static async Task<Result<string>> ValidateAndRetrievePayload(this HttpRequestMessage request, string key)
    {
        var headerValue = request.GetHeaderValue(SignatureHeader);
        if (string.IsNullOrWhiteSpace(headerValue))
            return Result.Failure<string>($"'{SignatureHeader}' Header not found or empty.");

        var payload = await request.Content.ReadAsByteArrayAsync();
        var byteKey = GetBytes(key);
        using (var hmac256 = new HMACSHA256(byteKey))
        {
            var hashPayload = hmac256.ComputeHash(payload);
            var base64String = Convert.ToBase64String(hashPayload);
            var hashResult = $"sha256={base64String}";
            if (hashResult.Equals(headerValue))
                return Result.Success(await request.Content.ReadAsStringAsync());
            return Result.Failure<string>(
                $"'{SignatureHeader}' does not match. Header: `{headerValue}` | Hash: `{hashResult}`");
        }
    }

    private static byte[] GetBytes(string value)
    {
        var bytes = new byte[value.Length * sizeof(char)];
        Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length);
        return bytes;
    }

    private static string GetString(byte[] bytes)
    {
        var chars = new char[bytes.Length / sizeof(char)];
        Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
        return new string(chars);
    }
}

【问题讨论】:

  • 嗨,Jason,刚刚发布了一个糟糕的答案,但它的一些代码在我的一个帐户上运行良好......你最终设法解决了这个问题吗?
  • @treendy 谢谢,我明天会试一试,但它看起来非常接近我已经尝试过的,除了你在我使用 UTF8 的地方使用 ASCII。会让你知道的。
  • 我终于明白了这一点,但离开了我为之工作的公司,不再能够访问代码。

标签: c# webhooks typeform


【解决方案1】:

如果您在测试 webhook 服务时遇到此问题,请记住 Typeform 发送的 JSON 正文已缩小 并且 以单个 Unix 换行符(@ 987654321@) 字符

在将请求正文直接发送到 HMACSHA256 实例时,这无关紧要,但如果您尝试使用内联 JSON 字符串验证您的代码,这一点很重要。 Typeform 在 webhook 管理 UI 中显示格式化的 JSON 输出尤其令人困惑。

C# 中字符串的签名验证可能如下所示:

using System;

class Program {
  static void Main(string[] args) {
    var key = "secret-key";
    // Note:  
    // - No spaces after `:`, `,`, etc.
    // - No indentation.
    // - No newlines except for a single line feed character on the end.
    var body = "{\"event_id\":\"01FCR2NZ5NNGBPTWEJXV0FR5V3\",\"event_type\":\"...\"}\u000a";
    
    // Convert strings to UTF-8 byte arrays.
    var keyBytes = Text.Encoding.UTF8.GetBytes(key);
    var bodyBytes = Text.Encoding.UTF8.GetBytes(body);

    string signature;
    using (var hmac = new Security.Cryptography.HMACSHA256(keyBytes))
    {
      // Calculate a hash and convert it to a base-64 string.
      var computedHashBytes = hmac.ComputeHash(bodyBytes);
      var computedHashBase64 = Convert.ToBase64String(computedHashBytes);
      signature = $"sha256={computedHashBase64}";    
    }
    Console.WriteLine(signature);
  }
}

【讨论】:

  • 谢谢。愚蠢的文档没有提到正文末尾的换行符之类的基本内容
【解决方案2】:

我希望这会有所帮助...我也是 TypeForm API 的新手,但是我有以下正在运行的代码...非常混乱,我还没有时间重构它...它适用于我的一个表单很好,但是由于某种原因,当我使用不同的 TypeForm 帐户时,它不起作用(即使我设置了相同的秘密)......这就是为什么我还没有重构它......

但只是分享,因为它适用于我的第一个帐户并且可能有用(我的第二个帐户也有同样的问题,结果不匹配......如果我找出原因,我会在这里告诉你并更新回答

    private bool IsValid(string jsonRequest)
    {
        string typeFormSig = Request.Headers["Typeform-Signature"];
        string generatedSig = $"sha256={CreateToken(jsonRequest)}";
        _logger.LogInformation($"SIGNATURE STUFF: sec: {SECRET} typeform: {typeFormSig}  MyGen: {generatedSig}");
        return (typeFormSig == generatedSig);
    }

    private static string CreateToken(string message)
    {
        var encoding = new System.Text.ASCIIEncoding();
        byte[] keyByte = encoding.GetBytes(SECRET);
        byte[] messageBytes = encoding.GetBytes(message);
        using (var hmacsha256 = new HMACSHA256(keyByte))
        {
            byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashmessage);
        }
    }

这就是我获取 json 字符串的方式:

[HttpPost("")]
    public async Task<IActionResult> Receive()
    {
        using (var reader = new StreamReader(Request.Body))
        {
            string jsonRequest = await reader.ReadToEndAsync();

【讨论】:

  • 只是好奇,你是如何从 HttpRequest 中提取 jsonRequest 的?这在内部进行了一些讨论,因为在我们的代码中有几种不同的方法可以实现这一点。
  • 刚刚用我如何从请求中获取 json 更新了我的答案......我还没有重构任何这些,只是最初试图让它首先工作......所以我无法确认这是否是一个很好的方法,但是大声笑......如果发现它实际上工作很奇怪,但是当我测试相同的代码时,在不同的帐户上使用大量表单,它不起作用
  • 这真是令人沮丧。谢谢你上面的建议,我确实试过了,奇怪的是我仍然得到不同的哈希值。我不知道为什么我的价值观不同,因为我完全按照建议去做。
【解决方案3】:

尝试使用 UTF 编码器,而不是建议的原始 ASCII 编码器:

private static string CreateToken(string message)
{
    var encoding = new System.Text.UTF8Encoding();
    byte[] keyByte = encoding.GetBytes(SECRET);
    byte[] messageBytes = encoding.GetBytes(message);
    using (var hmacsha256 = new HMACSHA256(keyByte))
    {
        byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
        return Convert.ToBase64String(hashmessage);
    }
}

private bool IsValid(string jsonRequest)
{
    string typeFormSig = Request.Headers["Typeform-Signature"];
    string generatedSig = $"sha256={CreateToken(jsonRequest)}";
    _logger.LogInformation($"SIGNATURE STUFF: sec: {SECRET} typeform: {typeFormSig}  MyGen: {generatedSig}");
    return (typeFormSig == generatedSig);
}

【讨论】:

    【解决方案4】:

    这是我最终使用的解决方案。这个问题的大多数答案的某些方面最终为解决问题提供了线索。

    public async Task<bool> ValidateSignature(HttpRequest request, Signature signatureData)
    {
        var headerValue = request.Headers[signatureData.HeaderKeyName];
        var keyBytes = Encoding.UTF8.GetBytes(signatureData.Secret);
        var messageBytes = Encoding.UTF8.GetBytes(await request.ReadAsStringAsync());
        byte[] hashMessage;
    
        switch (signatureData.HashType)
        {
            case HashType.HMAC_Sha1:
                hashMessage = new HMACSHA1(keyBytes).ComputeHash(messageBytes);
                break;
    
            case HashType.HMAC_Sha256:
                hashMessage = new HMACSHA256(keyBytes).ComputeHash(messageBytes);
                break;
    
            case HashType.HMAC_Sha384:
                hashMessage = new HMACSHA384(keyBytes).ComputeHash(messageBytes);
                break;
    
            case HashType.HMAC_Sha512:
                hashMessage = new HMACSHA512(keyBytes).ComputeHash(messageBytes);
                break;
    
            case HashType.HMAC_MD5:
                hashMessage = new HMACMD5(keyBytes).ComputeHash(messageBytes);
                break;
    
            default:
                throw new ArgumentOutOfRangeException(nameof(signatureData), "Hash type not currently supported.");
        }
    
        var builder = new StringBuilder();
        foreach (var t in hashMessage) builder.Append(t.ToString("x2"));
    
        var finalValue = builder.ToString();
        if (signatureData.HasPrefix) finalValue = $"{signatureData.PrefixValue}{builder}";
    
        return finalValue == headerValue;
    }
    

    【讨论】:

      猜你喜欢
      • 2019-12-24
      • 2020-03-08
      • 2019-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-04
      • 2016-10-28
      • 1970-01-01
      相关资源
      最近更新 更多