LINE PayやPayPayなど日本で広く使われる決済サービスは、Webhook通知をHMAC-SHA256で署名しています。加盟店は連携設定時にシークレットキーを受け取り、届く各通知には署名が付与されるため、受信側はそれをローカルで再計算して比較する必要があります。署名が一致しなければ、その通知は実際にはその決済サービスから送られたものではなく、本物の決済確認として処理してはいけません。
なぜ単なるハッシュではなく秘密鍵が必要なのか
SHA-256のような通常のハッシュは、データが転送中に壊れていないことしか証明しません——誰が送ったかは証明しません。もしサーバーがWebhookの本文をハッシュ化してリクエストに含まれるハッシュと比較するだけなら、攻撃者は自分で偽のペイロードを作り、一致するハッシュを添えることができてしまいます。HMACは送信者と受信者だけが知る秘密鍵を混ぜ込むことでこれを解決します——攻撃者がどのハッシュ関数が使われているか正確に知っていたとしても、鍵がなければ有効な署名を作ることは事実上不可能です。
なぜ単なる「鍵のハッシュ+メッセージ」ではないのか
ハッシュ化の前に鍵とメッセージを単純に連結する方法(例:hash(鍵 + メッセージ))は、一部のハッシュ関数において長さ拡張攻撃に対して脆弱です——攻撃者は鍵全体を知らなくても、データを追加して新しい有効なハッシュを計算できる場合があります。HMACは二重ハッシュと内側/外側のパディングを用いた特定の構造により、この種の攻撃を完全に封じています。
HMACが使われる他の場面
Webhookの署名以外にも、HMACはAPIリクエストの認証に使われ、認証アプリの6桁コードを生成するTOTPアルゴリズムの中核でもあります。
なぜこれが必要か
- 決済サービスや他のサードパーティサービスからのウェブフックの真正性を検証する。
- 秘密鍵自体をネットワーク経由で送信することなくAPIリクエストに署名する。
- TOTPコードやセッショントークンの内部の仕組みを理解する。
定数時間比較
受け取った署名を期待値と単純な===で比較するのは危険です——この演算子は通常、最初に不一致が見つかった時点で処理を止めるため、比較にかかった時間から攻撃者は最初の何バイトを正しく推測できたかを知ることができます。適切に実装されたWebhookハンドラーは、不一致の位置に関わらず常にすべてのバイトを検証する定数時間比較関数(PHPのhash_equalsやNode.jsのcrypto.timingSafeEqualなど)を使用します。