JWT の署名と検証
JWT は3つの base64url セグメント——ヘッダー、ペイロード、署名——からなり、最初の2つはトークンを持つ者なら誰でも読めます。デコードすれば何を主張しているかが分かり、信じてよいかどうかを教えてくれるのは3番目のセグメントだけです。この記事はその3番目のセグメントについてです。署名がどのように作られ、誰が作れるのか、検証者がどのように確認するのか、そして「検証済み」が「信頼できる」という意味でなくなる正確な境界です。すべてのステップは、ブラウザ内でローカルに署名と検証を行う JWT 署名・検証ツールで自分で実行できます。
デコードは検証ではない
JWT について最もよくある誤解は、最も危険でもあります。トークンを読むことが、それを確認することと同じだという誤解です。違います。ヘッダーとペイロードは base64url です——暗号化ではなく符号化です——だから、トークンを横取りした者、ログの中に見つけた者、ブラウザのローカルストレージからコピーした者は、鍵なしで数秒でデコードできます。中のクレーム(誰のためのものか、いつ期限切れになるか、どのロールを主張するか)は主張にすぎず、主張は安いものです。
署名が存在するのは、まさにクレームが安いからです。署名は、ヘッダー、ペイロード、そして発行者だけが持つ鍵を暗号学的に結び付けるものです。ペイロードの1文字を変えれば、署名は一致しなくなります。検証とは、その計算をやり直す行為です——受け取ったヘッダーとペイロードについて、発行者の鍵を使って署名を再計算し、比較します。一致すれば、トークンは発行者が署名した通りのものです。一致しなければ、何かが変わっており、クレームがどれほどもっともらしく見えても、トークンは無価値です。
対称と非対称:誰がトークンを発行できるか
HS256 では、署名するシークレットと検証するシークレットが同じです。だから HMAC は高速でシンプルで、1つのサービスの中では十分です——API が自分のトークンに署名して自分で確認し、シークレットは建物を出ません。2番目の当事者が検証を必要とする瞬間に、このモデルは壊れます。シークレットを渡すことは、トークンを発行する力を渡すことになるからです。検証と署名は同じ操作なのです。漏洩した HMAC シークレットは検証の問題ではありません。偽造の問題です。
非対称アルゴリズムは、この2つの力を分けます。RS256、ES256、EdDSA は秘密鍵で署名し、公開鍵で検証します。だから発行者は公開鍵を——JWKS として——公開でき、世界中のすべての検証者は、偽造する力を持つことなくトークンを確認できます。その分離こそが、JWT をサービス間で機能させるものです。API が ID プロバイダーからのトークンを信頼するのは、シークレットを共有しているからではなく、そのプロバイダーの公開鍵を信頼しているからです。代償は複雑さです——鍵の形式、ローテーション、kid の照合——それを具体的にするのが、このサイトの署名・検証ツールです。
アルゴリズム混同攻撃
古典的な JWT 攻撃には、壊れた暗号は必要ありません——トークンに自分のアルゴリズムを名乗らせる検証者が必要です。攻撃者は、正規の RS256 トークンを取り、その公開鍵は(公開鍵ですから)公開されていることを利用して、同じペイロードを HS256 で再署名します。シークレットとして公開鍵そのものを使います。ヘッダーから alg を読み取り、それに応じて検証ルーチンを選ぶ検証者は、公開鍵をシークレットとして使って HMAC で偽造トークンを「検証」してしまいます——そして公開鍵は公開されているのです。
防御は助言ではなく構造です。検証者は受け入れるアルゴリズムを固定しなければならず、鍵のファミリーはアルゴリズムのファミリーと一致しなければなりません——RSA 鍵が HMAC トークンを検証することは決してできず、シークレットが RSA トークンを検証することも決してできません。鍵をファミリーを越えて再解釈する検証者は、暗号がどれほど正しくても、設計として壊れています。だからこそ、このサイトの検証ツールは、ファミリーを越えた鍵を明示的な警告とともに拒否し、チェックを試みることすらしないのです。その拒否こそが機能なのです。
JWKS と kid ローテーションの実際
ID プロバイダーは、永遠に1つの鍵を渡すわけではありません。鍵のセット——JWKS、通常は /.well-known/jwks.json に——を公開し、新しい鍵を追加し、猶予期間の後に古い鍵を削除することでローテーションします。各トークンのヘッダーには、署名した鍵を名指す kid が入っているので、検証者はセットを取得し、一致する鍵を選び、検証します。古い鍵で署名されたトークンは、鍵が削除されるまで動き続けます。それが、ローテーションをフラグデーではなく安全なものにする仕組みです。
壊れるのは運用の細部です。検証者は JWKS をキャッシュしなければなりません(リクエストごとに取得するのは自ら招いた障害です)。kid が不明な場合——通常はキャッシュがまだ見ていないローテーションです——失敗する前に再取得し、信頼できない URL から鍵セットを受け入れてはいけません。セットは、それが届いたチャネルと同じだけしか信頼できません。だからこそ JWKS エンドポイントは HTTPS の背後にあり、このサイトのJWKS ビルダーは各 kid を鍵自体の RFC 7638 サムプリントとして導出し、ID を鍵と独立に偽装できないようにしているのです。
exp、nbf、iat——そして時計の問題
署名が証明するのは、トークンが署名されたということです。それが今も受け入れるべきかどうかについては何も語りません。それは時間クレームの仕事です。exp(いつ有効でなくなるか)、nbf(いつから有効になるか)、iat(いつ発行されたか)。署名を確認して時計を飛ばす検証者は、トークンを永遠に受け入れます——1年前に盗まれたものも含めて。実践的な答えは短い有効期間です。アクセストークンは分単位、長期の信頼はリフレッシュトークンが担い、失効は再発行しないことで処理します。
時計は一致しないので、検証者は少しの余裕を認めます——通常は exp と nbf に1〜2分の許容範囲です——しかし許容範囲は、ずれへの寛容であって、有効期限を無視する許可ではありません。時計が数分ずれたデバイスは、自分のサーバーが拒否するトークンを作ります。iat が未来にあるトークンは、丸め誤差ではなく偽造の兆候です。同じ時計の規律はツールにも当てはまります。検証ツールは、目に見える時計に対して有効期限を確認し、トークンが単に「無効」なのではなく期限切れであることをはっきり伝えます。
「検証済み」が証明することと、証明しないこと
検証済みのトークンが証明するのは3つのことだけです。ヘッダーとペイロードが署名された通りであること、署名者が鍵を持っていたこと、トークンが有効期間内にあることです。発行者があなたが思う者であることは証明しません——それは鍵の出所次第であり、攻撃者が管理する URL から取得した鍵は、攻撃者のトークンを完璧に検証します。クレームが真実であることも証明しません——署名は発言を認証するのであって、その背後の事実を認証するのではありません。そしてトークンが盗まれていないことも証明しません。有効なトークンは、誰が提示しても受け入れられます。だからこそトークンは短命で、スコープが限定されるのです。
だから信頼の判断はトークンの外にあります。API がトークンを信頼するのは、鍵を ID プロバイダーの well-known URL から HTTPS 経由で取得したからであり、iss が設定したプロバイダーと一致するからであり、aud がそのサービス自身と一致するからです。検証は最初のチェックではなく、最後のチェックです。鍵の出所、発行者、オーディエンス、次に署名、そして時間。その文脈なしに「有効」と報告するツールは、数学については真実を語っていますが、信頼については何も語っていません——だからこそ、このサイトの検証ツールは、判定の横にその旨をページに書いているのです。
よくある質問
鍵なしでトークンを検証できますか?
いいえ——検証には発行者の公開鍵(HMAC の場合は共有シークレット)が必要です。デコードには鍵は不要です。それこそが避けるべき混同です。デコードされたトークンは読めるのであって、検証されたのではありません。発行者が JWKS を公開している場合は、well-known URL から取得してトークンの kid と照合してください。このサイトの検証ツールがそれを行います。
RS256 トークンがシークレットで失敗するのはなぜですか?
シークレットと RSA 鍵は別のファミリーであり、健全な検証者は両者を混ぜることを拒否するからです。RS256 トークンは発行者の RSA 公開鍵——PEM、JWK、JWKS——で検証し、テキストのシークレットでは決して検証しません。シークレットを使うことは、アルゴリズム混同攻撃そのものです。公開鍵は公開されているので、HMAC 検証がそれをシークレットとして受け入れれば、誰でもトークンを偽造できてしまいます。
JWT はどれくらい生きるべきですか?
アクセストークンは日ではなく分です。5〜15分が一般的な範囲で、リフレッシュトークンがより長いセッションを担います。短い有効期間は、盗まれたトークンの被害を、期限切れまでの時間枠に閉じ込めます。失効はブロックリストを維持するのではなく、再発行しないことで処理します。何週間も生きるトークンは、道具の劣るセッションクッキーです。