JWTは誰でも読めるのに、なぜ改ざんできないのか
JWTのヘッダとペイロードはBase64エンコードされているだけで暗号化されておらず、誰でも中身を読めます。改ざんを防いでいるのは署名部分だけという3部構成の仕組みと、暗号化ではない注意点を整理します。
ログイン後にブラウザの開発者ツールを開くと、eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U のような妙に長い文字列を見かけることがあります。これがJWT(JSON Web Token)です。暗号のように見えますが、実は誰でも中身を読めてしまいます。
結論を先に書きます。
JWTのヘッダとペイロードはBase64URLという符号化がされているだけで、暗号化はされていません。誰でもデコードすれば中身のJSONがそのまま読めます。改ざんを防いでいるのは3つ目のパーツである署名の部分だけで、「読めるけど、書き換えられない」というのがJWTの本質です。
JWTは「.」で区切られた3つのパーツでできている
JWTは、見た目は1本の長い文字列ですが、中身はピリオドで区切られた3つのパーツに分かれています。
| パーツ | 内容 | 符号化 |
|---|---|---|
| ヘッダ(Header) | 署名アルゴリズムなどのメタ情報 | Base64URL(暗号化ではない) |
| ペイロード(Payload) | ユーザーIDや有効期限などのデータ本体 | Base64URL(暗号化ではない) |
| 署名(Signature) | ヘッダ+ペイロードを秘密鍵などで計算した値 | 署名アルゴリズムに依存 |
ヘッダとペイロードは、それぞれJSONのオブジェクトをBase64URLという形式に変換しただけのものです。Base64は暗号ではなく、元に戻す手順が広く公開されている単なる符号化方式なので、誰でも一瞬でデコードして中身のJSONを読むことができます。
「暗号化」と「符号化」はまったく別物
ここで混同しやすいのが、「読めない=暗号化されている」という思い込みです。Base64はぱっと見が意味不明な文字列に見えるため誤解されがちですが、鍵を使わなくても誰でも元に戻せるという点で、暗号とは根本的に性質が違います。JWTのヘッダとペイロードは、この符号化がされているだけの状態です。
だからこそ、JWTのペイロードにパスワードや個人情報のような秘密にすべきデータを入れてはいけません。誰かに渡ったJWTを開発者ツールやオンラインのデコーダーに貼り付けるだけで、中身は丸見えになるからです。
改ざんを検知しているのは署名だけ
一方で、JWTには「勝手に書き換えられない」という性質もあります。これを担っているのが3つ目のパーツ、署名です。
サーバーはJWTを発行するとき、ヘッダとペイロードの内容をもとに、秘密鍵(あるいは秘密鍵と対になる鍵のペア)を使って署名の値を計算します。受け取った側は、同じ計算をやり直して署名が一致するかを確認します。もし誰かがペイロードの中身(例えばユーザーIDや権限の値)を1文字でも書き換えると、署名を計算し直さない限り検証時に不一致が検出され、改ざんされたトークンとして弾かれます。署名を正しく計算し直すには秘密鍵が必要なので、鍵を持たない第三者には書き換えができない、という仕組みです。
つまりJWTは、**「中身は誰でも読める」けれど「鍵を持たない人は正しく書き換えられない」**という、暗号化とは違う方向で安全性を確保しています。
実際にデコードして確かめる
言葉で説明されるより、実際に自分の手元のJWTをデコードしてみたほうが納得が早い部分です。JWTをデコードを整える にトークンを貼り付けると、ヘッダとペイロードを整形されたJSONとして表示し、有効期限などの時刻情報も日本時間に変換してくれます。デコードはすべてブラウザ内で完結し、貼り付けたトークンがどこかに送信されることはありません。ただし署名の検証までは行わないため、「中身が改ざんされていないか」を確かめる用途には使えない点は覚えておいてください。自分のログイン中のJWTを一度覗いてみると、ペイロードに何が入っているかを具体的に把握できて、扱い方の感覚がつかみやすくなります。