不喜欢广告? 无广告 今天

氢键MAC Webhook如何知道你没有说谎

发布日期

每次 GitHub、Stripe 或 Shopify 向您的服务器发送 Webhook 时,它们都会使用 HMAC 对有效载荷进行签名。这里详细说明了其工作原理,以及如何在您的代码中进行验证。

HMAC:Webhook如何知道你没有说谎 1
广告 移除?

你收到一个声称来自Stripe的POST请求,请求体显示一笔付款已成功。你处理订单,发货——三天后才发现该请求是伪造的。哎,真疼。

这就是为什么每个严肃的Webhook服务提供商都会使用 氢键MAC对它们的请求体进行签名。如果你理解HMAC,你就明白为什么GitHub、Stripe、Shopify、Twilio以及几乎所有现代API都使用它——你也会知道如何在自己的服务器代码中验证这些签名。

HMAC实际上是什么

HMAC代表 基于哈希的消息认证码它回答一个问题:“发送这条消息的人是否知道共享密钥?”

它通过在秘密密钥和消息体的组合上运行一个密码学哈希函数(通常为SHA-256)来工作。输出是一个固定长度的字符串,它会:

  • 在消息的任意一个字节发生变化时完全改变 如果消息的任意一个字节发生变化
  • 无法在不知道密钥的情况下生成 无法被用来还原密钥或原始消息
  • 无法被还原 以揭示密钥或原始消息

公式非常简洁: HMAC(key, message) = H((key ⊕ opad) || H((key ⊕ ipad) || message))你不需要记住内部细节——每种语言都提供标准库实现——但了解其意图是有帮助的:密钥以两种不同方式被混合进哈希中,以防止一类称为“长度扩展攻击”的攻击。

Webhook服务提供商如何使用它

当你注册一个Webhook端点时,提供商会给你一个 签名密钥 ——只有你和他们知道的随机字符串。当事件触发时:

  1. 提供商将事件负载序列化为JSON(或规范字符串)。
  2. 它计算 HMAC-SHA256(secret, payload).
  3. 然后将带有签名的请求发送到头部—— X-Hub-Signature-256 对于GitHub, Stripe-Signature 对于Stripe,以及其他服务。

在你这边,你对原始请求体执行相同的计算并进行比较。如果匹配,负载是可信的。如果不匹配,就丢弃它。

代码中的验证

以下是常见语言中验证的示例。模式在每种语言中都是一致的:计算,使用常数时间函数进行比较。

Node.js

const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)          // rawBody must be a Buffer or string — NOT parsed JSON
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Python

import hmac
import hashlib

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)

PHP

function verifyWebhook(string $rawBody, string $signature, string $secret): bool {
    $expected = hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signature);
}

关键细节:使用原始体

最常见的实现错误是将 解析并重新序列化 的负载传递给HMAC函数,而不是原始字节。键的顺序、空格和Unicode转义都会影响哈希。提供商签名的是它实际通过网络发送的字节——你必须对完全相同的字节进行哈希。

在Express(Node.js)中,这意味着配置体解析器以保留原始缓冲区:

app.use('/webhooks', express.raw({ type: 'application/json' }));

在Django中,使用 request.body 而不是 request.data在Flask中,使用 request.get_data().

为什么不用简单的哈希?

对负载的简单SHA-256哈希无法证明任何东西——任何人都可以计算 SHA256(payload) 而无需密钥。HMAC的密钥才是使其成为 身份验证 消息认证码,而不仅仅是校验和。它回答的是“是谁发送的”而不是“传输过程中是否被损坏”。

为什么不使用非对称签名(如RSA)?

RSA和ECDSA允许接收方在不拥有私钥的情况下验证签名——这对于公开广播(如代码签名)非常有价值。对于Webhook,只有两个实体需要验证签名:你和提供商。共享密钥更简单、更快,且在该模型下同样安全。一些提供商(如Svix、Clerk)提供非对称Webhook签名,用于你无法安全地在服务器端存储密钥的情况。

重放攻击——以及如何阻止它们

有效的HMAC签名可以证明真实性,但不能证明时效性。攻击者可以捕获一个合法的签名请求并稍后重放。Stripe通过在 Stripe-Signature 头部中包含时间戳,并将时间戳与负载体一起哈希来应对这种情况。在你这边,你将任何时间戳超过五分钟的请求拒绝。

如果你正在构建自己的Webhook系统,也应这样做:在签名消息中包含单调递增的随机数或Unix时间戳,并在服务器端拒绝过期请求。

安全比较不是可选项

永远不要使用简单的相等性检查(===, ==)来比较HMAC签名。字符串比较的短路会泄露关于匹配前导字节数量的信息——攻击者可以发起成千上万次请求,逐字节重建预期签名。始终使用常数时间比较:

  • Node.js: crypto.timingSafeEqual()
  • Python: hmac.compare_digest()
  • PHP: hash_equals()
  • Go: hmac.Equal()
  • Ruby: ActiveSupport::SecurityUtils.secure_compare()

整合起来:一个生产级的Webhook处理器

以下是一个使用GitHub的 X-Hub-Signature-256 头部在Node.js中的完整示例:

const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET;

// Keep the body as raw bytes — critical!
app.use('/github/webhook', express.raw({ type: 'application/json' }));

app.post('/github/webhook', (req, res) => {
  const sigHeader = req.headers['x-hub-signature-256'];
  if (!sigHeader) return res.status(401).send('Missing signature');

  const sig = sigHeader.replace('sha256=', '');
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Safe to process the event now
  console.log('Event type:', req.headers['x-github-event']);

  res.sendStatus(200);
});

app.listen(3000);

快速参考:各服务使用情况

服务提供商标头Algorithm签名中是否包含时间戳?
GitHubX-Hub-Signature-256HMAC-SHA256
StripeStripe-SignatureHMAC-SHA256是的
ShopifyX-Shopify-Hmac-Sha256HMAC-SHA256
TwilioX-Twilio-SignatureHMAC-SHA1
松弛X-Slack-SignatureHMAC-SHA256是的
PaddlePaddle-SignatureHMAC-SHA256是的
想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?