ひとことで言うと
HMACとは、暗号技術における「秘密の握手」のようなもの。共有された秘密鍵を使って、メッセージが本物であり、かつ改ざんされていないことを証明します。
こいつが解決する問題
インターネットの黎明期、あのワイルドな西部開拓時代には、メッセージを送るのはハガキを送るようなものでした。途中で手に入れた人は誰でも読めるし、何か書き足してから相手に渡すことだってできたわけです。「真夜中に会おう、現金を持ってこい」なんて書かれたハガキを受け取ったとして、それが本当に味方の秘密諜報員から送られてきたもので、宿敵の「悪者イブ」からじゃないって、どうやって確かめられますか? それに、もともとは「お昼に会って、楽しくランチでもどう?」って書かれていたんじゃないかって、どうやって確かめればいいんでしょう?
これが、真正性(本当に本人から?)と完全性(改ざんされてない?)という、二つの問題です。
単純な暗号学的ハッシュ関数(SHA-256など)は、最初のステップとしては良さそうに見えます。メッセージをハッシュ化して、メッセージとハッシュ値を両方送る。受信者はメッセージをもう一度ハッシュ化して、値が一致するか確認する。これでよし! これで完全性の問題は解決です。メッセージの1バイトでも変更されれば、ハッシュ値は一致しなくなります。
でも、これでは真正性の問題は解決しません。悪者のイブはメッセージを書き換えて、その新しいメッセージから新しいハッシュ値を計算して、両方を送ればいいだけです。受信者はハッシュ値がメッセージと一致することを確認できますが、そのメッセージとハッシュ値のセット全体が偽物だとは気づけません。
ここで颯爽と登場するのが、HMAC(Hash-based Message Authentication Code)です。ハッシュ化のプロセスに共有の秘密鍵を導入することで、HMACはその秘密鍵を持つ者だけが生成できる署名を作り出します。これは、ただの蝋の封印(誰でも作れる)と、王家固有の印章指輪で押された蝋の封印(王様しか持っていない)の違いのようなものです。HMACは、完全性と真正性の両方を保証してくれるのです。
内部の仕組み
HMACは新しい種類のハッシュ関数ではありません。既存のハッシュ関数(SHA-256など)を、賢く、特定の方法で使うためのレシピです。公式の仕様はRFC 2104ですが、ここではもっと分かりやすく解説しましょう。
材料
HMACを作るには、3つのものが必要です。
- メッセージ: 保護したいデータ。WebhookのJSONペイロード、URLパラメータの文字列、あるいは任意のバイナリデータなど。
- 秘密鍵: 送信者と受信者だけが知っているバイト列。これが魔法の材料です。もしこれが漏洩したら、システム全体が破綻します。
- ハッシュ関数: SHA-1、SHA-256、SHA-512といった標準的なアルゴリズム。どのハッシュ関数を選ぶかによって、最終的なHMAC署名の長さが決まります(例:HMAC-SHA256は256ビットの署名を生成します)。
レシピ(HMACアルゴリズム)
hash(key + message) ってやればいいんじゃない?と思うかもしれません。シンプルに見えますが、この方法は「長さ伸長攻撃(length extension attack)」と呼ばれる巧妙な暗号解読の罠に弱いです。公式のHMACの作り方は、これらの攻撃を防ぐために、もうちょっと手が込んでいます。二重にハッシュ化するプロセスを使うのです。
ステップを簡単に見てみましょう。
鍵の準備: ハッシュ関数は固定長のブロック単位(例:SHA-256なら64バイト)でデータを処理します。このブロックサイズに合わせて鍵を準備する必要があります。
- 鍵がブロックサイズより長い場合、鍵自体をハッシュ化し、その結果を新しい鍵として使います。
- 鍵がブロックサイズより短い場合、ゼロバイトを付け足して(パディングして)ブロックサイズに合わせます。
内部キーと外部キーの作成: この準備した鍵から、2つの別々のキーを派生させます。
ipad(inner pad):定数のバイト(0x36)をブロックサイズ分繰り返したもの。opad(outer pad):別の定数のバイト(0x5C)をブロックサイズ分繰り返したもの。
準備した鍵と
ipadをXOR演算してinner_padded_keyを作ります。同様に、準備した鍵とopadをXOR演算してouter_padded_keyを作ります。二重ハッシュの実行: さて、いよいよメインイベントです。
- 内部ハッシュ:
inner_padded_keyと元のメッセージを連結し、ハッシュ関数にかけます。 - 外部ハッシュ:
outer_padded_keyと内部ハッシュの結果を連結し、それをハッシュ関数にかけます。
- 内部ハッシュ:
この外部ハッシュの最終結果が、あなたのHMAC署名です!
疑似コードで書くと、こんな感じです。
function hmac(key, message, hash_function, block_size) {
// 1. 鍵の準備
if (key.length > block_size) {
key = hash_function(key);
}
if (key.length < block_size) {
key = pad_with_zeros(key, block_size);
}
// 2. 内部キーと外部キーの作成
o_key_pad = key XOR (0x5C repeated to block_size);
i_key_pad = key XOR (0x36 repeated to block_size);
// 3. 二重ハッシュの実行
inner_hash_result = hash_function(i_key_pad + message);
final_hmac = hash_function(o_key_pad + inner_hash_result);
return final_hmac;
}
なぜ二重にハッシュ化するの?
この「内部→外部」という構造こそが秘伝のタレです。内部ハッシュは秘密鍵とメッセージを組み合わせます。そして外部ハッシュは、その最初の操作の結果を、再び秘密鍵と組み合わせてハッシュ化する、というわけです。これが内部ハッシュを「封印」する役割を果たします。これにより、攻撃者が鍵を知らずに中間的なハッシュ結果を操作することが計算上不可能になり、長さ伸長攻撃やその他の潜在的な暗号解読を防ぐのです。これは実績があり、時の試練に耐えてきた堅牢な構造です。
実社会でのストーリー
GitHub Webhookの守護神
あるスタートアップチームは、誰かがmainブランチにプッシュするたびに、アプリを本番環境に自動デプロイするCI(継続的インテグレーション)サーバーをセットアップしていました。トリガーはGitHubからのWebhook、つまりGitHubのサーバーから彼らのCIサーバーの公開URLに送られるPOSTリクエストでした。ある夜、イタズラ好きのインターンがその公開URLを見つけ、簡単なcURLコマンドで偽のWebhookペイロードを送りつけ、リソースを食いつぶすだけの無駄なデプロイを何十回も実行させました。
先輩開発者は15分でそれを修正しました。GitHubのWebhook設定で、彼女は長くてランダムな「secret」を生成。そのsecretをコピーし、CIサーバーの環境変数として設定しました。これでGitHubは、Webhookのペイロードごとにこのsecretを使ってHMAC-SHA256署名を生成し、X-Hub-Signature-256ヘッダーに入れて送るようになりました。CIサーバーのコードも、受け取ったリクエストの生ボディに対して同じsecretを使ってHMAC計算を行うように更新されました。もし計算した署名がヘッダー内のものと一致すればリクエストは処理され、一致しなければ403 Forbiddenで拒否されます。イタズラはピタリと止みました。
教訓: Webhookは必ずHMAC署名検証で保護しましょう。認証されるまで、いかなる受信リクエストも信用してはいけません。
APIのクッキージャーを守る
ある開発者が、認証にシンプルな署名付きクッキーを使うサービスを構築していました。ユーザーがログインすると、サーバーは彼らのuser_idとexpiryタイムスタンプを含むクッキーを発行します。ユーザーがクッキーを編集して別のユーザーになりすます(例:user_id=123をuser_id=1に変える)のを防ぐため、開発者はHMAC署名を付け加えました。
クッキーのペイロードはこんな感じです:user_id=123&expiry=1678886400。サーバーは、サーバー上に保存された秘密鍵でこの文字列に署名します。最終的にブラウザに送られるクッキーはdata="user_id=123&expiry=1678886400"&signature="sha1=2a8b..."のようになります。
ユーザーが次のリクエストを送ると、ブラウザはそのクッキーを返します。サーバーはdata部分を取り出し、秘密鍵を使ってHMAC署名を再計算し、クッキーのsignature部分と比較します。もし一致すれば、サーバーはユーザーIDと有効期限が正当で、改ざんされていないと判断できます。
教訓: HMACは、改ざん防止機能を持つ「ステートレス」なトークンやクッキーを作成するのに最適な方法です。これはJSON Web Token (JWT) をはじめ、多くの認証システムの基礎となっています。
乗っ取られなかった銀行振込
あるeコマースプラットフォームが、ベンダーへの支払いを開始するために、決済代行業者のAPIと連携していました。APIコールはシンプルなJSONメッセージでした:{"vendor_id": "ven_abc", "amount": 500.00, "currency": "USD"}。プラットフォームはMITM(中間者攻撃)を懸念していました。HTTPSで通信が暗号化されていても、巧妙な攻撃者なら(例えば、危殆化した認証局があるような理論上のシナリオでは)リクエストを傍受して改ざんできてしまいます。amountを50000.00に変えたり、vendor_idを自分自身のものに変えたりするかもしれません。
決済代行業者のAPIは、すべてのリクエストにHMAC-SHA512での署名を要求していました。プラットフォームはJSONペイロードを正規化された文字列にシリアライズし、自社のプライベートAPIキーで署名を計算し、それをAuthorizationヘッダーに入れて送信します。決済代行業者のサーバーも全く同じ手順を実行します。もし計算した署名がヘッダーで送られてきたものと一致すれば、彼らは2つのことを確信できます:リクエストは正当なプラットフォームから来たこと(真正性)、そしてvendor_idとamountは途中で変更されていないこと(完全性)です。
教訓: リスクの高い操作において、HMACはメッセージの送信者と内容が期待通りであることを保証するための、極めて重要なセキュリティ層を提供します。
よくある間違いと落とし穴
- 秘密鍵の漏洩。 鍵がすべてです。クライアントサイドのJavaScriptで公開したり、パブリックなGitリポジトリにコミットしたり、平文でログに出力したりしたら、セキュリティはゼロになります。パスワードと同じように扱いましょう。
- コンスタントタイムでない比較関数の使用。 ユーザーが提供した署名と、こちらで計算した署名が一致するかチェックする際に、
if (a === b)のような標準的な文字列比較を使うとセキュリティホールになり得ます。これは多くの場合、一致しない文字が見つかった瞬間にfalseを返すため、攻撃者が署名を1文字ずつ推測するためのわずかな時間差(タイミング)を生み出してしまいます。これが「タイミング攻撃」です。常に、暗号ライブラリが提供する、どこで不一致が起きても同じ時間がかかる専用の「コンスタントタイム」比較関数を使いましょう。 - 署名するデータが違う。 送信者と受信者は、全く同じバイト列に対してHMACを計算しなければなりません。よくあるバグは、片方が整形されたJSONオブジェクトに署名し、もう片方がコンパクトな1行バージョンのJSONに署名してしまうケースです。あるいは、片方が末尾に改行を含み、もう片方が含まない、といったケースもあります。正規化されたメッセージ形式に合意し、それを厳守しなければなりません。
- リプレイ攻撃を忘れる。 HMAC自体は、攻撃者が有効な署名付きメッセージをキャプチャして、それを何度も再送信するのを防ぐことはできません。もしそのメッセージが「ボブに$10支払う」というものなら、攻撃者がその支払いを1000回もトリガーできてしまっては困ります。これを防ぐには、リクエストごとに変わる値(タイムスタンプや「ノンス(number used once)」など)を、署名されるデータの中に含めます。そうすればサーバーは、タイムスタンプをチェックして古いリクエストを拒否したり、使用済みのノンスのリストを保持して重複を拒否したりできます。
なぜ知っておくべきか
信頼できる通信が必要な場面では、いつでもHMACを思い浮かべるべきです。データを秘密にすること(それは暗号化の仕事)ではなく、データが正当であることを保証するためのものです。
- APIを構築したり利用したりしますか? 特にWebhook(Stripe、GitHub、Twilioなど)では、リクエストが本物であることを検証するための業界標準としてHMACが使われています。
- 認証に関わる仕事をしていますか? 最も有名なJWTをはじめとする多くのトークンベースのシステムは、HMAC(例:「HS256」アルゴリズム)を使ってトークンのペイロードに署名し、ユーザーが自分自身の権限を改ざんするのを防いでいます。
- データの完全性を検証する必要がありますか? 信頼できない環境(ユーザーのブラウザのクッキーなど)を経由してデータを渡し、それが改ざんされずに戻ってくることを保証する必要があるなら、HMACがあなたのツールです。
HMACは、Web開発者のセキュリティツールボックスにおける基本的な要素の一つです。その仕組みを理解すれば、よりセキュリティを意識した、優れたエンジニアになれるでしょう。
もっと深く
- RFC 2104: HMAC: Keyed-Hashing for Message Authentication: オリジナルの技術仕様書です。内容は濃いですが、これが究極の一次情報です。
- Wikipedia: HMAC: 歴史、設計思想、実装の詳細について、分かりやすくまとめられています。
- OWASP: Replay Attack: Open Web Application Security Projectによるリプレイ攻撃に関するページ。HMACを使う上で理解しておくべき重要な概念です。
- Stripe Docs: Webhookの署名のチェック: 何十億ドルもの取引を保護するためにHMACに大きく依存している企業による、実践的でリアルなガイドです。
- Crypto 101: HMAC: HMACの設計の背後にある暗号学的原則について、もう少し分かりやすく解説しています。