こんにちは!日々、巧妙化するサイバー攻撃の最前線で「守りの盾」を磨き続けている、セキュリティ責任者の「黒峰(くろみね)」です。
これからエンジニアを目指す方や、現場で初めてセキュリティを任された方にとって、「暗号」や「認証」という言葉は、なんだか数学の難しい数式が並んでいるような、近寄りがたいイメージがあるかもしれませんね。
でも、安心してください。セキュリティの本質は、実は私たちの身の回りにある「家の鍵」や「身分証明書」の仕組みと驚くほど似ているんです。
今日は、現代のWebアプリケーションで非常によく使われている「JWT(ジョット:JSON Web Token)」をテーマに、もしその大切な「鍵」が盗まれてしまったらどうなるのか、そしてどうやって被害を防ぐのかについて、泥棒と防犯対策の物語に例えて楽しく紐解いていきましょう!
—
1. JWTは「偽造できない魔法の通行証」
まず、JWT(JSON Web Token)が何者なのかをおさらいしましょう。
Webサイトにログインした後、サーバーから「あなたはこの人ですね」という証明として渡されるデジタルな通行証のようなものです。
この通行証には、あなたの名前や権限が書いてありますが、勝手に書き換えられないように、サーバーが「秘密の印鑑(署名)」を押しています。
共通鍵と公開鍵:印鑑の種類の違い
ここで、暗号の基本である「共通鍵」と「公開鍵」の違いを家の鍵で例えてみます。
- 共通鍵暗号(HS256など):
合鍵を自分と相手の二人で持っている状態です。同じ鍵で「鍵をかける(署名)」ことも「鍵を開ける(検証)」こともできます。シンプルですが、合鍵が漏れたらおしまいです。
- 公開鍵暗号(RS256 / ECCなど):
「自分だけが持っている実印(秘密鍵)」と「誰でも見られる印影のカタログ(公開鍵)」に分かれている状態です。署名は自分しかできませんが、確認は誰でもできます。
多くの現場では、この「秘密鍵」をいかに守るかがセキュリティの生命線になります。
—
2. もし「秘密の印鑑」が泥棒に盗まれたら?
想像してみてください。もし、あなたの会社の「通行証に押すための実印」が泥棒(ハッカー)に盗まれてしまったら……?
泥棒は、白紙のカードに勝手に「社長」と書き込み、盗んだ印鑑をポンと押すだけで、完璧な偽造通行証をいくらでも作れてしまいます。これが「秘密鍵の漏洩」によるなりすまし攻撃の恐ろしさです。
「パスワードを複雑にする」といった個人の努力では防げない、システム全体の根幹が揺らぐ大事件になります。
—
3. 被害を最小限にする「鍵ローテーション」の知恵
「鍵を盗まれないようにする」のは当然ですが、プロの視点では「もし盗まれても、すぐに無効化できるようにしておく」という備えを重視します。それが「鍵ローテーション(定期的な更新)」です。
家の鍵を10年も使い続けるのではなく、定期的にシリンダーごと交換するイメージですね。
実践的な「KID」を使った運用
JWTのヘッダーには kid (Key ID) という項目を入れることができます。これを使うと、「どの鍵で署名したか」を明示できます。
例えば、以下のように複数の鍵を用意しておきます。
1. 古い鍵 (Key_A): そろそろ引退予定
2. 現在の鍵 (Key_B): 今メインで使っている鍵
3. 新しい鍵 (Key_C): 次に使う予定の鍵
もし Key_B が盗まれたと気づいたら、即座にすべての通行証を Key_C で発行するように切り替え、Key_B で押された印鑑を「無効」として扱うようにシステムを設定します。
—
4. 泥棒の通行証を指名手配する「JTIと無効化リスト」
鍵を新しくしても、「すでに泥棒が作ってしまった有効期限内の通行証」は、そのままでは使えてしまいます。これを防ぐのが、JTI (JWT ID) によるブラックリスト管理です。
通行証一枚一枚に「シリアル番号(JTI)」を振っておき、「この番号の通行証は盗まれたから受け付けないで!」とガードマン(サーバー)に伝えておく仕組みです。
実装イメージ:Node.jsでの検証例
それでは、実際にどのように「鍵の使い分け」や「無効化チェック」を行うのか、簡単なコード例を見てみましょう。
const jwt = require('jsonwebtoken');
// 1. 複数の秘密鍵を管理(実際には環境変数や専用の鍵管理サービスに保存します)
const keys = {
"key_v1": "old_secret_shhh",
"key_v2": "new_powerful_secret_2023", // 現在メインで使っている鍵
};
// 2. JWTの発行(JTIを付与して、どの鍵を使ったかkidに記録する)
function createToken(user) {
const payload = {
sub: user.id,
name: user.name,
jti: "unique_id_123456" // 通行証ごとのシリアル番号
};
return jwt.sign(payload, keys["key_v2"], {
algorithm: 'HS256',
keyid: 'key_v2', // ヘッダーに鍵のIDを含める
expiresIn: '1h'
});
}
// 3. 通行証のチェック(ブラックリストと鍵の有効性を確認)
const blacklistedJtis = new Set(["unique_id_123456"]); // 盗まれた通行証のリスト
function verifyToken(token) {
try {
// ヘッダーをデコードして、どの鍵が使われたか確認する
const decodedHeader = jwt.decode(token, { complete: true }).header;
const kid = decodedHeader.kid;
// 漏洩した古い鍵や、存在しない鍵IDなら即座に却下!
if (kid === 'key_v1') {
throw new Error("この鍵はもう使えません。再ログインしてください。");
}
// 署名の検証
const decoded = jwt.verify(token, keys[kid]);
// JTI(シリアル番号)がブラックリストに入っていないかチェック
if (blacklistedJtis.has(decoded.jti)) {
throw new Error("この通行証は無効化されています(指名手配中)!");
}
return decoded;
} catch (err) {
console.error("認証失敗:", err.message);
return null;
}
}
—
5. まとめ:一歩ずつ、守りを固めていきましょう
「鍵が漏洩したらどうしよう…」と怖がる必要はありません。大切なのは、以下の3つのステップを意識することです。
1. 「秘密鍵」は厳重に保管する:
コードの中に直接書かず、鍵管理専用のサービス(AWS KMSやAzure Key Vaultなど)を使いましょう。
2. 「鍵の更新(ローテーション)」を設計に入れる:
kid を使って、いつでも鍵を切り替えられる柔軟性を持ちましょう。
3. 「緊急停止ボタン(JTI)」を用意する:
特定の怪しいトークンを無効化できる仕組みを持っておくことで、万が一の際の被害を最小限に抑えられます。
セキュリティは、一度設定して終わりではありません。日々の運用のなかで「もしここが破られたら?」と想像力を働かせることが、最強の防御になります。
最初は難しく感じるかもしれませんが、こうして一つずつ仕組みを理解していけば、あなたはもう立派な「守り手」の一員です。一緒に、安全で楽しいWebの世界を作っていきましょうね!
また次回の記事でお会いしましょう。お疲れ様でした!
コメント