HMAC 生成
メッセージと鍵から HMAC-SHA-1、SHA-256、SHA-384、SHA-512 を計算し、与えられた署名がどれによるものかを判定します。
署名を計算するには鍵を入力してください。
このツールの役割
HMAC はメッセージと共有シークレットを短い署名に変えます。同じシークレットを持つ者は誰でもそれを計算し直し、メッセージが変わらず届き、シークレットを知る誰かから来たかを確かめられます。このツールはよく使う 4 つの変種を一度に計算し — 送られてきた署名を与えれば — どれがそれを生んだかを教えます。
その 2 つ目の向きこそ、たいてい人が訪れる理由です。Webhook が検証に失敗していて、問いは実は「この署名は有効か」ではなく「もっともらしいいくつかのことのうち、送信者と違うことを私はどれでしているか」です。貼り付けた署名の前に付いた sha256= 形式の接頭辞は認識され、取り除かれます。ヘッダーの値を届いたそのまま貼り付けられます。
うまくいかないのは鍵
HMAC はバイトをバイトで署名します。メッセージはたいてい明白 — 生のリクエスト本文 — ですが、鍵はほぼ決してそうではありません。シークレットは文字列として届き、文字列はどう読むかを決めるまではバイトではないからです:
a3f2 テキストとして 4 バイト 61 33 66 32 a3f2 16進数として 2 バイト a3 f2
どちらの読みも正当で、どちらも申し分なく整った署名を生み、二つの署名には共通点がありません。何も警告しません: エラーも、長さの苦情もなく、ただ送信者が計算したものと一致しない値があるだけです。だからここでの鍵のエンコードは推測ではなく見える選択です — 署名が一致しないとき、まず切り替えるのはこれです。
大まかな目安として: whsec_ のような接頭辞や大小混じりの文字と数字の連なりを持つシークレットは、ふつうテキストとして意図されています。ちょうど 32 文字か 64 文字で 0-9 と a-f だけを使う文字列は、たいてい 16 進数です。= で終わるものは、ほぼ確実に Base64 です。ただし形は証明ではないので、形ではなく送信者の文書を確認してください。
メッセージは正確なバイトでなければならない
不一致のもう半分はメッセージです。HMAC はバイトの上に定義されるので、バイトを変えるものは何であれ署名を完全に変えます — 部分点も、惜しい一致もありません。
- JSON の再直列化。本文を解析して再び文字列化すると、キーが並べ替わり、間隔が変わり、数値が正規化されることがあります。受け取った生の本文に署名して検証し、決してその往復した複製にはしないでください。
- 末尾の改行。本文をファイルに保存すると加えるツールがあります。それはバイトで、すべてを変えます。
- 文字エンコード。非 ASCII テキストを含む本文は、両側が使う同じエンコード — 実際には UTF-8 — として読まれなければなりません。
- 圧縮やミドルウェア。ハンドラの前の何かが本文を展開したり書き換えたりするなら、その後ではなく前に検証してください。
多くのプロバイダーは本文だけに署名するのでもありません。Stripe はタイムスタンプと本文をドットで結んで署名し、AWS はメソッド・パス・ヘッダーとペイロードのハッシュから作った正規リクエストに署名します。素の本文に対して検証が失敗するなら、署名された文字列はおそらく素の本文ではありません — それは送信者側に文書化されており、さらにデバッグする前に読む価値があります。
SHA-1 がハッシュツールでなくここで提供される理由
ハッシュツールは SHA-1 を破られていると印付けします。こちらは HMAC-SHA-1 を警告なしに載せ、それは見落としではなく意図的です。
SHA-1 は署名と証明書には使えません。衝突を構成できるからです: 二つの異なる文書がダイジェストを共有するようにできます。HMAC はその性質に依存しません。その安全性はシークレット鍵に基づき、構成 — メッセージを二度、両方に鍵を混ぜてハッシュする — は、下地のハッシュが衝突に弱くても持ちこたえます。HMAC-SHA-1 は今も健全で、OAuth 1.0a や古い AWS 署名が今も使うものなので、それを計算するのを拒むツールは、より安全になることなく、ただ役に立たなくなるだけです。
新しいものには何であれ SHA-256 が妥当な既定です。長いダイジェストはここで意味のあるほど強くはなく — 安全性の天井はダイジェストの長さではなく鍵です — ので、SHA-384 と SHA-512 は、相互運用しなければならない何かがそれを求めるときにだけ選ぶ価値があります。
署名を安全に比べる
このページはふつうの文字列比較で比べ、ここではそれで問題ありません: あなた自身が鍵を持っているので、漏らすものは何もありません。入ってくるリクエストを検証するサーバーでは、それでは問題があります。ふつうの比較は最初の異なるバイトで止まるので、かかる時間が推測のどれだけが正しかったかを明かし、多くのリクエストを送れる攻撃者が有効な署名を 1 バイトずつ復元できます。
プラットフォームが提供する定数時間の比較 — Node の crypto.timingSafeEqual、Python の hmac.compare_digest、PHP の hash_equals — を、16 進数の文字列ではなく生のバイトに対して使ってください。それとともに、リクエストで名指しされたアルゴリズムを信じるのではなく自分で計算した署名に対して比べ、古い有効な署名が再生されないよう、タイムスタンプが今から遠いメッセージを拒んでください。
HMAC はハッシュでも署名でもない
ここで三つのものが混同され、その違いは後で何を主張できるかにとって重要です:
- ハッシュはメッセージを取ってダイジェストを生みます。誰でも計算できるので、メッセージが偶然に変わっていないことは証明しますが — 特定の誰かから来たことは証明しません。
- HMAC はメッセージと共有シークレットを取ります。両当事者が同じ鍵を持つので、送信者がシークレットを知っていたことを証明します。どちらの当事者が送ったかは証明できません。どちらも生めたからです。
- 署名は、送信者だけが持つ秘密鍵と、誰でも検証できる公開鍵を使います。それが否認防止を与えます: 送信者は後でそれを否定できません。
だから HMAC は、既にシークレットを共有する二つのシステムの間 — Webhook、内部 API、セッショントークン — で正しいツールであり、第三者に誰が何を送ったかを証明する必要があるときには誤ったツールです。また、認証はしても隠しはしないことに注意してください: メッセージは平文で伝わり、HMAC は機密性について何も言いません。
Stripe の Webhook 署名を検証する
Stripe はここで唯一、貼り付けでは済まないレシピで、たいていは訪れる前に既に試してみたほうのものです。Stripe-Signature ヘッダーは署名ではなくカンマ区切りの要素の一覧であり、Stripe が署名した文字列はリクエスト本文だけではありません。ヘッダーを行に折るのは読みやすさのための Stripe 自身の文書のやり方で、本物は一行で届きます。
Stripe-Signature: t=1492774577, v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd, v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
- メッセージ: t= の値、次にドット、次に届いたとおりの生のリクエスト本文。上のヘッダーならその文字列は 1492774577. で始まり、本文がすぐ続きます。Stripe はこれを署名済みペイロードと呼び、Stripe の署名が本文だけとは決して一致しない理由はここに尽きます。
- 鍵: そのエンドポイント一つだけの署名シークレットを、whsec_ の接頭辞ごと丸ごと。API キーでもなく、別のエンドポイントのシークレットでもありません — 同じ URL を二度登録すれば二つになります。
- 鍵のエンコード: テキスト。署名シークレットは Stripe が選んだ文字列で、無作為に見えても 16 進数でも Base64 でもありません。どちらかとして読めば別のバイトになり、何とも一致しない署名が出ます。
- 照合する署名: ヘッダー全体ではなく v1 要素だけ。v1= はそのまま貼り付けられます。この見出しは sha256= とまったく同じように取り除かれるからです — ですがヘッダー全体はだめで、先頭の t= が見出しと取られ、その後ろに続くものは署名では全くありません。
ほかを調べる前に、このヘッダーについて知っておくとよいことが三つあります。v1 以外のスキームはすべて無視してください: テスト用イベントに付く v0 要素は意図的に本物の署名ではなく、残りを無視することこそが、より弱いスキームを押し付けられるのを防ぎます。エンドポイントのシークレットを入れ替えている間は両方が最大一日有効で、ヘッダーはシークレットごとに v1 要素を一つ運び、そのうち一致するのは一つだけです。そして配信の試行ごとに新しく署名されるので、再試行は最初の試行の署名を運びません — 署名される文字列の中のタイムスタンプは、古い署名を拒むことを身振りではなく本当の防御にしているものでもあります。署名を壊さずにそれを変えることはできないからです。
GitHub の Webhook 署名を検証する
GitHub はこのツールがそのまわりに作られた貼り付けであり、ここで唯一、端から端まで確かめられるレシピです。GitHub が計算済みの例を公開しているからです。署名は X-Hub-Signature-256 ヘッダーに、見出し sha256= とそれに続く 16 進数のダイジェストとして届きます。この見出しはここで認識され取り除かれるので、ヘッダーの値は届いたそのまま入ります。
X-Hub-Signature-256: sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
- メッセージ: 生のリクエスト本文を 1 バイトずつそのまま。GitHub が公開する例は本文 Hello, World! に署名し、その後には何もありません — 末尾の改行もなしです。
- 鍵: Webhook の設定に打ち込んだシークレットを、打ち込んだとおりそのまま。公開されている例では It's a Secret to Everybody です。
- 鍵のエンコード: テキスト。GitHub のシークレットはあなたが選んだ文字列なので、決して 16 進数ではありません — 16 進数の数字だけでできたシークレットであっても、打ち込んだ文字として読まれます。
- 照合する署名: sha256= を含むヘッダーの値全体。16 進数は大文字小文字を問わず比べられるので、ログがどちらで印字していても構いません。
この三つの値をそろえるのが、ある中でいちばん速い健全性の確認です: 貼り付ければこのページは HMAC-SHA-256 の 16 進数で一致と報告し、それは本物の配信でどちらかを試す前に、計算機とあなたのレシピの読みがともに正しいことを教えます。GitHub は古いほうのヘッダー X-Hub-Signature も送ります。同じ本文に対する HMAC-SHA-1 で、後方互換のためだけに残されているものです。このページは四つのダイジェストを一度に計算するので、どちらのヘッダーから来た署名でも、どちらかを言わずに識別されます。救えない一つは、GitHub が送ったバイト以外のものとして届いた本文です — ペイロードは ASCII の外の文字を運ぶことがあり、両側がそれを UTF-8 として読まなければなりません。
Shopify の Webhook 署名を検証する
Shopify はダイジェストを 16 進数ではなく Base64 で書き、ここで意味のある違いはそれだけです: 同じ 32 バイトが、等号一つで終わる 44 文字になります。その文字は見出しではなく詰め物なので、前から何も切り取られません。
X-Shopify-Hmac-SHA256: dXEH6g6yUJ/CESIczphLijdXC211hsIsRvQ3nIsEPhc=
- メッセージ: 配信されたとおりの生のリクエスト本文を 1 バイトずつ。Shopify 自身の警告は本文を解析するミドルウェアについてです — まず検証し、あとで解析してください。本文を既にオブジェクトに変えた解析器は、バイトを捨ててしまっています。
- 鍵: その Webhook が属するアプリのクライアントシークレット。そのアクセストークンでもなく、同じ画面の隣にある API キーでもありません。
- 鍵のエンコード: テキスト。ほかの二つと同じで、シークレットはバイトのエンコードではなく文字列です。
- 照合する署名: ヘッダーの値全体。そのまま貼り付けられ、どのダイジェストに対しても 16 進数と Base64 の両方が試されるので、このページが返すエンコードそのものが、送信者がどちらで書いたかの答えになります。
上の枠の値は形を見せるためにそこにあります: GitHub の例と同じ 32 バイトを、16 進数ではなく Base64 で書いたものです。並べて見る価値があります。二つのヘッダーの違いの中身はそれだけ — 一つのダイジェスト、二つの書き方 — であり、このページが何かが一致したとだけ言うのではなくアルゴリズムと並べてエンコードを挙げる理由もそこにあります。
よくある質問
- 署名が一致しません。まず何を確認しますか?
- 鍵のエンコード、次にメッセージのバイトです。16 進数に見えるシークレットはしばしばテキストではなく 16 進数として意図され、二つはどちらもエラーなしにまったく異なる署名を生みます。その後、再直列化した複製ではなく受け取ったとおりの生の本文に署名していることを確かめてください。
- ハッシュツールが破られていると言う SHA-1 が、ここで提供されるのはなぜですか?
- HMAC が、SHA-1 が失った性質である衝突耐性に頼らないからです。その安全性は鍵から来ます。HMAC-SHA-1 は今も健全で、OAuth 1.0a や古い AWS 署名で今も使われていますが、新しいものには SHA-256 が正しい既定です。
- 長いダイジェストのほうが安全ですか?
- 意味のあるほどではありません。HMAC の強さはダイジェストの長さではなくシークレットで区切られるので、SHA-512 は SHA-256 の 4 倍良いわけではありません。相手が期待するものを選んでください。
- HMAC とデジタル署名の違いは何ですか?
- HMAC は両側が知る一つのシークレットを使うので、送信者がシークレットを知っていたことは証明しますが、どちら側が送ったかは証明しません。デジタル署名は送信者だけが持つ秘密鍵を使うので、第三者が検証でき、送信者は否定できません。その最後の性質が必要なら、HMAC は誤ったツールです。
- 署名を === で比べるべきですか?
- サーバーではだめです。ふつうの比較はバイトが異なるとすぐ返り、そのタイミングが推測した署名のどれだけが正しかったかを漏らします。プラットフォームの定数時間の比較を生のバイトに使ってください。このページでは、既に鍵を持っているので問題ありません。
- 私の鍵はどこかに送られますか?
- いいえ。すべてが Web Crypto API でブラウザ内で計算されます。メッセージも鍵も端末の外に出ることはありません。
- Stripe の Webhook 署名はどう検証しますか?
- ヘッダーの t= 要素のタイムスタンプ、ドット、生のリクエスト本文の三つを一つの文字列につなぎ、それにエンドポイントの署名シークレットをテキストとして読んで署名し、結果をヘッダーの v1 要素と比べます。上の節がそれを項目ごとにたどります。ほとんどの人がつまずくのは最初の一歩です: 本文だけは Stripe が署名したものではありません。
- GitHub の Webhook 署名はどう検証しますか?
- 生のリクエスト本文に Webhook のシークレットをテキストとして読んで SHA-256 で署名し、16 進数のダイジェストを X-Hub-Signature-256 ヘッダーと比べます。ここに貼り付けるときは見出し sha256= を付けたままにできます。GitHub は計算済みの例 — 本文 Hello, World! とシークレット It's a Secret to Everybody — を公開していて、本物の配信で試す前にレシピの読みを確かめるいちばん速い方法です。
- Shopify の Webhook 署名はどう検証しますか?
- 生のリクエスト本文に、その Webhook が属するアプリのクライアントシークレットをテキストとして読んで SHA-256 で署名し、Base64 のダイジェストを X-Shopify-Hmac-SHA256 ヘッダーと比べます。ヘッダーの値全体はそのまま貼り付けられます — 末尾の等号は Base64 の詰め物です。一致しないときのふつうの原因は、ハンドラが見る前に本文を解析してしまったミドルウェアです。
- ほかのプロバイダーの Webhook 署名はどう検証しますか?
- 四つの問いで片が付き、送信者の文書に四つとも載っています: どのヘッダーが署名を運ぶか、実際に署名される文字列は何か、シークレットはどう読まれるはずか、ダイジェストは 16 進数か Base64 か。それを上の欄に入れてください。それでも一致しないなら、答えはほぼいつも二つ目です — きわめて多くの送信者が、本文だけではなく何かをつないだ本文に署名しています。
関連するツール
- TOTP / 2FA コードデバッガー
2FA コードを生成し、完全な導出とともにデバッグします。
- Bcrypt
bcrypt ハッシュを生成、または既存のハッシュとパスワードを照合。
- JWT デコーダー / 検証ツール
JSON Web Token のデコードと検証 — 署名とクレーム。
- ハッシュ生成ツール
MD5、SHA-1、SHA-256、SHA-384、SHA-512 を同時に。