HMAC — ホットウェブ훅が嘘をついていないことを確認する方法
どのサーバーもPOSTリクエストをあなたのホットウェブエンドポイントに送信できます。HMAC署名は、正当な送信者がパラメータを書いたことを証明し、それを検証する方法です。
Stripeがカードに請求を行うたびにウェブホークが発火し、GitHubがプルリクエストをマージするたびにウェブホークが発火する。これらのプラットフォームは、あなたが与えたURLにPOSTリクエストを送信しているが、ここに不快な事実がある。誰でもその同じURLにPOSTリクエストを送信できる。
したがって、あなたのサーバーがリクエストがStripeから来たのか、攻撃者があなたのエンドポイントURLを推測したかを知る方法は何か?答えはHMACである。そして、それを理解した後、すべての重要なAPIプラットフォームがこのアプローチを採用している理由がわかるようになる。
問題:誰でもPOSTリクエストをあなたのエンドポイントに送信できる
ウェブホークエンドポイントは単なるURLである。公開可能である(送信者がそれを到達できるようにしなければならない)ため、POSTリクエストを受け付ける。悪意あるアクターが偽のパラメータを構築し、あなたのエンドポイントに送信するということが、何も阻止されていない。
あなたのウェブホークハンドラーがイベントを受け取ったときに、こうする場合がある:
if event["type"] == "payment.completed":
fulfill_order(event["data"]["order_id"])
エンドポイントURLを知っている攻撃者は、偽のイベントを送信できる。 payment.completed イベントに任意のオーダーIDを設定できる。検証がない場合、あなたのサーバーは決済されていないオーダーを無条件に処理してしまう。
あなたは、どちらも共有する秘密をもつ誰かがパラメータを書いたことを確認する方法が必要である。
HMACとは何か?
HMACは ハッシュベースメッセージ認証コード. これは、暗号ハッシュ関数(通常はSHA-256)と共有秘密キーを組み合わせて署名を生成する構成である。署名は以下の2点を証明する:
- 本物であること — メッセージは共有秘密キーを持つ誰が作成したことを示す
- 整合性 — メッセージが送信中に変更されていないことを示す
HMACの重要な性質:秘密を知らなければ、有効な署名を生成することはできない。また、署名を逆算して秘密を回復することはできない。これは一方向の証明である。
HMACとシンプルなハッシュの違い
シンプルなハッシュ(例:SHA-256)は、整合性の問題を解決するが、信頼性は解決していない。攻撃者が有効なパラメータをキャプチャした場合、変更されたパラメータのハッシュを再計算できる。HMACは、ハッシュプロセスのすべてのステップにあなたの共有秘密キーを組み込むため、秘密キーを知らなければ、正確なハッシュアルゴリズムを知らなくても、一致する署名を生成できない。
ウェブホークHMACの仕組み
プロセスは3つのステップ:キー交換、署名、検証。
ステップ1:キー交換(一度だけ行われる)
StripeやGitHubといったプラットフォームでウェブホークを設定するとき、それらはあなたのための ウェブホークシークレット を生成し、一度だけあなたに表示する。あなたはサーバー側に保存する(クライアント側コードや公開リポジトリには保存しない)。それ以上、シークレットは通信経路を通過しない。
ステップ2:送信者がパラメータを署名する
ウェブホークを送信する前に、プラットフォームはあなたの共有シークレットを使って、raw request bodyに対してHMAC署名を計算する:
signature = HMAC-SHA256(secret_key, request_body)
署名は、通常、ヘッダーのように X-Hub-Signature-256 (GitHub) または Stripe-Signature (Stripe) に添付される。raw payloadは体に変更されないまま送信される。
ステップ3:受信時に検証を行う
あなたのサーバーがウェブホークを受け取ったとき、raw bodyと保存されたシークレットを使って同じHMACを再計算し、ヘッダーにある署名と比較する。一致すれば、パラメータは信頼できるかつ変更されていない。一致しない場合は、リクエストを拒否する。
expected = HMAC-SHA256(your_secret, raw_body)
if not constant_time_equal(expected, header_signature):
return 401
知らせ 定時比較 — これはなぜ重要なのか、後で説明する。
を使用してください。
Stripe
ストリップは Stripe-Signature ヘッダーにタイムスタンプと1つ以上の署名を含む:
Stripe-Signature: t=1679000000,v1=abc123...,v0=oldformat...
署名は timestamp.payload (ピリオドで結合) に対してHMAC-SHA256で計算される。タイムスタンプを含めることで、Stripeはリプレイ攻撃を防ぐことができる — 攻撃者が有効な署名リクエストをキャプチャし、後に再送すると、タイムスタンプが古いため、それを拒否できる。
GitHub
GitHubは X-Hub-Signature-256 フォーマットのヘッダーを送信する。 sha256=<hex_digest>. 署名は、ウェブホーク設定で設定したシークレットを使ってraw bodyのHMAC-SHA256である。
Shopify
Shopifyは X-Shopify-Hmac-Sha256 ヘッダーを使用し、Base64エンコードされたHMAC-SHA256署名を持つ — 同じ概念、異なるエンコーディング。
コードでの検証
ここに、3つの一般的な言語での検証の例がある。パターンはまったく同じで、ただライブラリの呼び出しに違いがある。
パイソン
import hmac
import hashlib
def verify_webhook(secret: str, payload: bytes, signature_header: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
received = signature_header.removeprefix("sha256=")
return hmac.compare_digest(expected, received)
Node.js
const crypto = require('crypto');
function verifyWebhook(secret, rawBody, signatureHeader) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const received = signatureHeader.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
PHP
function verifyWebhook(string $secret, string $rawBody, string $signatureHeader): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signatureHeader);
}
セキュリティを破壊する誤り
1. == を定時比較に代用する
標準の文字列等価性(==, ===)は、不一致を検出するたびに短絡的に動作する。これは タイミング側チャネルを生成する。攻撃者は、あなたのサーバーが異なる署名を拒否する時間を測定できる。文字列が長いプレフィックスを共有している場合、拒否に少し時間がかかる。多くのリクエストを送ることで、攻撃者は1バイトずつ署名を再構成できる。
常に定時比較を使用する: hmac.compare_digest() Pythonで crypto.timingSafeEqual() Node.jsで hash_equals() PHPで
2. ボディを解析する前に検証する
HMACは、 raw bytes のリクエストボディに対して計算される。JSONを最初にパースし、その後再シリアル化する場合、異なるキー順序、空白、エンコーディングが得られる可能性がある。常にフレームワークのボディパーサーが触れる前にraw bodyをキャプチャし、それに対して検証を行うべきである。
3. リプレイ攻撃をチェックしない
有効な署名リクエストは永遠に有効である — ただし、タイムスタンプをチェックする場合を除く。プラットフォームが署名スキームにタイムスタンプを含めている場合(Stripeはそうしている;GitHubはそうしていない)、数分前のタイムスタンプを持つリクエストを拒否する。これにより、攻撃者が合法なリクエストをキャプチャし、再送するのを防ぐ。
4. シークレットをソースコードにハードコーディングする
ウェブホークシークレットは環境変数またはシークレットマネージャーに保存され、バージョン管理にコミットすべきではない。シークレットが漏洩されると、攻撃者は永遠にフォールド可能なパラメータを生成できる — あなたがそれを回転するまで。
HMACが防ぐことができない点
HMACは、署名が共有秘密を持つ誰によって作成されたことを証明する。しかし、それは以下を防ぐことはできない: ない -
- 送信者が侵害された場合 — Stripeの署名インフラが侵害された場合、偽のイベントも有効な署名を持つままになる
- リプレイ攻撃(Replay attacks) — タイムスタンプまたはノンスを追加で検証する場合を除く
- 機密性 — HMACは何も暗号化していない。パラメータはプレーンテキストで送信される(ただしHTTPSがこの問題を解決する)
ほとんどのウェブホーク統合では、HTTPS上のHMACがすべての必要条件を満たす。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
