【入門編】 JWTの秘密鍵漏洩と鍵ローテーション戦略 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

こんにちは!日々、巧妙化するサイバー攻撃の最前線で「守りの盾」を磨き続けている、セキュリティ責任者の「黒峰(くろみね)」です。

これからエンジニアを目指す方や、現場で初めてセキュリティを任された方にとって、「暗号」や「認証」という言葉は、なんだか数学の難しい数式が並んでいるような、近寄りがたいイメージがあるかもしれませんね。

でも、安心してください。セキュリティの本質は、実は私たちの身の回りにある「家の鍵」や「身分証明書」の仕組みと驚くほど似ているんです。

今日は、現代の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の世界を作っていきましょうね!

また次回の記事でお会いしましょう。お疲れ様でした!

コメント

タイトルとURLをコピーしました