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 比較すべきです。
手で検証した後
同等のロジックをハンドラに落とし込みましょう。主要言語の標準ライブラリには既に primitive があります:Node の crypto.createHmac、Python の hmac.new、Go の hmac.New。比較は必ず constant-time な関数(timingSafeEqual、hmac.compare_digest、hmac.Equal)で——絶対に == を使わない。