CyberChef で X-Device-Signature を手で検証する
webhook 署名が一致しないとき、まず切り分けるべきはどちら側が間違っているか——あなたか、私たちか。CyberChef を使えば UnifyPort と完全に同じ署名アルゴリズムを、ハンドラが見ているのと同じ入力で、ブラウザ上から 30 秒ほどで再現できます。
UnifyPort は何に署名しているのか
署名シークレットが設定されたすべての配信には、三つのヘッダがつきます:
署名は次のように計算されます:
デバッグの前に画面の端に貼っておくべきメモ:
- timestamp と body の間の 1 バイトは ASCII のドット(
.)であり、改行ではない。 raw_bodyとは私たちが送った生バイト——あなた側で JSON パース、空白除去、Unicode 正規化が行われる前のもの。- hex 出力は小文字。大文字の digest と比較すると false-negative になる。
CyberChef レシピ
CyberChef でレシピペインに次の二つのオペレーションを順に置きます:
- 1. HMAC——Hashing function を
SHA256に設定。署名シークレットを Key に貼り、Type はUTF8。 - 2. To Hex——Delimiter は空、Bytes per line は
0。これで区切りのない小文字 hex 文字列が出力され、ヘッダに一致します。
Input ペインに貼るのはちょうど <timestamp>.<raw_body>——例えば:
Output ペインの値は配信の X-Device-Signature と一致するはずです。一致しないなら、バグは三つのうちどれか——シークレットが違う、body が途中で変えられた、timestamp を間違ったヘッダから読んだ。
この手動検証が本当に効くとき
- 署名シークレットをローテートした直後。DNS や古いデプロイを疑う前に、新しいシークレットがハンドラに届いているか確認できる。
- JSON ライブラリをアップグレードした直後。パーサが検証側に渡す前に body を吐き直すことがある——気づかないうちに署名が合わなくなる。
- 同僚が「私の環境では動く」と言ったとき。同じレシピと同じ入力を二人で走らせ、digest が違う方が間違ったシークレットを持っている。
ウェブページにシークレットを貼ることについて
CyberChef は完全にクライアントサイドの静的バンドル——あなたのブラウザ内だけで動き、公式ホスト版ではシークレットがマシンを離れることはありません。とはいえブラウザ拡張や共有セッションが心配なら、CyberChef をローカルで動かす(リポジトリを clone して index.html を開く)か、DevToys——完全オフラインなデスクトップ版——を使ってください。本番の署名シークレット運用には、CyberChef の出力を目で見るのではなく、コードで constant-time 比較すべきです。
よくある質問
- UnifyPort の webhook 署名を手で検証するには?
- CyberChef で HMAC(Hashing function は SHA256、署名シークレットを Key に UTF8 エンコーディングで指定)を追加し、続けて To Hex(Delimiter は空、Bytes per line は 0)を追加します。
<timestamp>.<raw_body>をそのまま Input ペインに貼ってください。Output はX-Device-Signatureヘッダとバイト単位で一致するはずです。 - 署名が UnifyPort の送ってくる値と一致しないのはなぜ?
- よくある原因は三つ:(1) 署名シークレットが間違っている——ダッシュボードで見える値と一致するか確認してください;(2) 検証前に body が変更された——JSON の再出力、空白除去、Unicode 正規化はいずれも digest を壊します;(3) timestamp を間違ったヘッダから読んだ——署名されるのは
X-Device-Timestampだけで、フレームワークの Date ヘッダではありません。 - 署名シークレットを CyberChef に貼っても安全?
- 公式ホスト版の CyberChef はクライアントサイドの静的バンドル——シークレットがブラウザを離れることはなく、サーバー側のログ記録もありません。それでも一切露出させたくなければ、リポジトリを clone してローカルで
index.htmlを開くか、DevToys——完全オフラインなデスクトップ版——を使ってください。
手で検証した後
同等のロジックをハンドラに落とし込みましょう。主要言語の標準ライブラリには既に primitive があります:Node の crypto.createHmac、Python の hmac.new、Go の hmac.New。比較は必ず constant-time な関数(timingSafeEqual、hmac.compare_digest、hmac.Equal)で——絶対に == を使わない。