こんにちは!インフラやアプリ開発の現場に飛び込んだばかりの頃は、覚えることが山ほどあって大変ですよね。「セキュリティ対策をしっかりしよう!」と意気込んだものの、専門用語の壁にぶつかって途方に暮れてしまう方も多いのではないでしょうか。
でも、安心してください。セキュリティの基本は、私たちの身の回りにある「現実世界の防犯」とまったく同じです。
今回は、Web開発やAPI連携でなくてはならない技術「HMAC(エイチマック)」について、新人のIT担当者の方にもスッと腹落ちするように、身近な例えを交えながら一歩ずつ紐解いていきましょう!
—
1. そもそも「メッセージが改ざんされていないか」どうやって確かめる?
例えば、あなたが運営するオンラインショップで、ユーザーが「100円の商品を買う」というリクエストをサーバーに送ってきたとします。
このとき、悪意のあるユーザーが通信の途中でデータをこっそり書き換えて、「100円」を「1円」にしてサーバーに送りつけたらどうなるでしょう? もしサーバーが「送られてきたからそのまま処理しよう」と信じ込んでしまったら大変な大赤字ですよね。
通信内容が途中で書き換えられていないか、そして「本当にそのユーザー(またはシステム)から送られてきたものか」を証明するために使われるのが「ハッシュ関数」です。
ハッシュ関数ってなに?
ハッシュ関数(SHA-256など)は、どんなに長いデータであっても、きっちり決まった長さの文字列(バラバラに見える暗号のようなもの)に変換してくれるマジックボックスのようなものです。
- 入力データ:「Apple」 → ハッシュ値:
331a65... - 入力データ:「Apple!」 → ハッシュ値:
7d86f5...
少しでもデータを書き換えると、ハッシュ値はまったく別のものに変わるという性質を持っています。これを利用して、「届いたデータが途中で書き換えられていないか」をチェックできるわけですね。
—
2. 素朴な疑問:「じゃあ、合言葉(秘密鍵)をメッセージにくっつけてハッシュ化すればいいのでは?」
「それなら簡単! メッセージの末尾に、自分たちだけが知っている合言葉(シークレット)をくっつけて、それを丸ごとハッシュ化すればいいんじゃない?」
そう思ったそこのあなた、非常に鋭い着眼点です!
セキュリティの世界では、これを 単純な連結(Secret + Message) と呼びます。
例えば、以下のようなイメージです。
送信データ = 秘密鍵 + メッセージ
ハッシュ値 = SHA256(Secret + Message)
一見すると、「秘密鍵を知らない第三者は正しいハッシュ値を作れないから安全そう」に見えますよね。しかし、ここにサイバー攻撃者が好んで狙う恐ろしい盲点が隠されています。
それが、今回お話しする「長さ拡張攻撃(Length Extension Attack)」です。
—
3. 家の鍵が破られる瞬間:長さ拡張攻撃の仕組み
ここで、少し現実の世界に例えてみましょう。
あなたは、合鍵の作り方を知られないように、頑丈なカプセルの中に「秘密の合言葉」と「メッセージ」を一緒に入れて封印し、それを宅急便で送ることにしました。
1. カプセルの中身:[秘密の合言葉] + [こんにちは]
2. そのカプセル全体に特殊なワックス(SHA-256ハッシュ)を塗り固めて、誰かが勝手に開けたら分かるようにしました。
攻撃者は、そのカプセル(メッセージとハッシュ値)を途中で盗み見ることができました。攻撃者は「秘密の合言葉」そのものは知りません。しかし、SHA-256のような古いアルゴリズムや、単純な連結構造を使っている場合、「中身を覗き見ることなく、そのカプセルの後ろに勝手に新しいメッセージを継ぎ足す」ことが数学的にできてしまうのです!
これが長さ拡張攻撃の正体です。
攻撃者は、あなたが送った「こんにちは」の後ろに、勝手に「、そして全財産を私にください」というメッセージを継ぎ足し、さらにその状態に合わせた「もっともらしい新しいワックス(ハッシュ値)」を自作してサーバーに送りつけることができます。サーバーは「おっ、正しいワックスが塗られているから本物のメッセージだな」と勘違いして受け入れてしまう……これが最悪のシナリオです。
—
4. 救世主「HMAC」の登場:なぜ特別な構造が必要なのか?
単純な連結が危険なら、どうすればいいのでしょうか?
そこで登場するのが、今回の主役である「HMAC(Hash-based Message Authentication Code)」です。
HMACは、ただ単純に秘密鍵とメッセージをくっつけるのではなく、「秘密鍵を混ぜ合わせるプロセスを2回行う(インナーハッシュとアウターハッシュ)」という、ちょっと凝った仕組み(RFC 2104で規定されています)を使っています。
身近な例えで言うなら、
1. まずメッセージに秘密鍵を混ぜて一度しっかりと箱に鍵をかける(インナー)
2. その箱をさらに別の秘密鍵の要素でぐるぐる巻きにして封印する(アウター)
という二重の防護壁を作るイメージです。
この絶妙な数学的構造のおかげで、攻撃者がいくらメッセージの末尾に文字を継ぎ足そうとしても、最初の鍵の掛け方が完全に保護されているため、長さ拡張攻撃を完全にシャットアウトすることができるのです。
—
5. 実装してみよう:PHPで学ぶ安全なHMAC-SHA256の書き方
百聞は一見にしかず。実際にプログラムの世界でどう実装するのか、よく使われるPHPを例に見てみましょう。面倒な自前実装は絶対にせず、プログラミング言語が用意してくれている安全な標準関数(hash_hmac)を使います。
以下のサンプルコードをご覧ください。
<?php
/**
* HMAC-SHA256を用いたメッセージ認証コードの生成サンプル
*/
// サーバー側だけが知っている秘密の合言葉(絶対に外部に漏らしてはいけません)
$secretKey = "my_super_secret_key_202X";
// ユーザーから送られてきた(または送信する)メッセージ
$message = "action=transfer&amount=100&to=attacker";
// 【NGな例】単純な連結(長さ拡張攻撃の餌食になります)
// $unsafeHash = hash('sha256', $secretKey . $message);
// 【OKな例】安全なHMAC-SHA256を生成する
// 第1引数にアルゴリズム、第2引数にメッセージ、第3引数に秘密鍵を指定します
$hmacSignature = hash_hmac('sha256', $message, $secretKey);
// 生成された署名を出力
echo "送信メッセージ: " . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . "\n";
echo "HMAC署名: " . $hmacSignature . "\n";
/**
* 応用編:サーバー側で受信した署名を検証する関数
* タイミング攻撃を防ぐために hash_equals を使うのがプロの流儀です
*/
function verifyRequest($receivedMessage, $receivedSignature, $secretKey) {
// 受信したメッセージから、同じ秘密鍵を使って本来あるべきHMACを再計算する
$calculatedSignature = hash_hm_ac_safe($receivedMessage, $secretKey); // ※説明用の仮想関数名です。実際は下の行を使用
$calculatedSignature = hash_hmac('sha256', $receivedMessage, $secretKey);
// hash_equals を使うことで、文字列比較にかかる時間を一定にし、
// タイミング攻撃(処理時間の差から秘密を暴く攻撃)を防ぎます
return hash_equals($calculatedSignature, $receivedSignature);
}
?>
ここで、実務における重要なポイントをひとつ。
署名同士を比較するときは、通常の比較演算子(== や ===)ではなく、必ず hash_equals() 関数を使いましょう。細かいハッキング手法の一つに「タイミング攻撃」というものがありますが、安全な比較関数を使うことで、こうしたプロレベルの攻撃からもシステムを守ることができます。
—
6. まとめ:今日から意識すべきセキュリティの鉄則
お疲れ様でした! 今回のポイントを最後にギュッとまとめておきますね。
1. 生データをそのままハッシュ化したり、単純に秘密鍵とくっつけたりしない!
- 単純な連結(
Secret + Message)は、長さ拡張攻撃という巧妙な罠にかかる危険があります。
2. メッセージの改ざん検知には必ず「HMAC」を使う!
- 二重のハッシュ構造を持つHMAC(例:
HMAC-SHA256)を利用することで、安全にデータの整合性を保証できます。
3. 車輪の再発明はしない!
- 独自の暗号アルゴリズムや複雑な連結ロジックを自作せず、言語やフレームワークが提供する信頼性の高い標準関数(PHPの
hash_hmacなど)を正しく使いましょう。
セキュリティの世界は一見すると難しそうに見えますが、仕組みの裏側にある「なぜそれが必要なのか(攻撃者の視点)」をひとつずつ理解していけば、決して怖くありません。
一歩ずつ、確実に、堅牢なシステムを作れるエンジニアを目指して一緒に頑張っていきましょう! 次回の解説もお楽しみに!
コメント