JWTの署名アルゴリズム、なぜ「RS256」一択なのか? 現場で語られない「HS256」の罠
現場のコードレビューをしていると、未だにJWT(JSON Web Token)の署名アルゴリズムに「HS256」を平然と使っているプロジェクトを見かける。一見、実装が楽だし、ライブラリのデフォルトだからという理由で採用されがちだが、セキュリティの観点から言えば、それは「爆弾を抱えて走り回っている」のと同じだ。
今日は、なぜプロフェッショナルがRS256を推奨し、HS256がなぜ「現場のエンジニアを殺す」のか、その真髄を話そう。
—
1. HS256の致命的な「盲点」:鍵漏洩という終わりの始まり
HS256(HMAC with SHA-256)は、共通鍵暗号だ。サーバーとクライアント(あるいはマイクロサービス間)で「同じ一つの秘密鍵」を共有する。
ここで最も恐ろしいのは、「サーバーサイドで署名検証を行う全てのコンポーネントが、鍵を保持しなければならない」という点だ。もし、フロントエンドのAPIサーバーが侵害されたら? あるいは、開発環境のログに秘密鍵が紛れ込んだら? その瞬間に、攻撃者は自分自身で正規のJWTを「偽造」できてしまう。
さらに、HS256の最大のリスクは「アルゴリズムの強制変更攻撃(Algorithm Confusion Attack)」にある。
PoC(概念実証):なぜHS256は狙われるのか
攻撃者は、JWTのヘッダーにある`”alg”: “HS256″`を`”alg”: “none”`や、あるいは公開鍵を署名検証用の鍵として使うように書き換える。もし、バックエンドのライブラリが「鍵の型」を厳密にチェックしていない場合、公開鍵を「HMACの秘密鍵」として誤認させ、署名を偽造されるリスクが常にある。これは理論上の話ではなく、過去に多くの有名ライブラリを沈めてきた現実の脆弱性だ。
—
2. RS256が「正義」である理由
RS256(RSA Signature with SHA-256)は、非対称鍵暗号だ。
- 秘密鍵: 認証サーバー(Identity Provider)のみが持つ。
- 公開鍵: リソースサーバー(各マイクロサービス)が持つ。
リソースサーバーは「検証」しかできない。もしリソースサーバーがハックされても、攻撃者はトークンを偽造できない。これが、堅牢なシステム設計の基本だ。
—
3. 実践:RS256を使ったセキュアな実装(Python/PyJWT)
「実装が面倒」というのは言い訳にならない。現代のライブラリは、公開鍵をJWKS(JSON Web Key Set)エンドポイントから自動取得する仕組みをサポートしている。
以下のコードは、公開鍵を使ってトークンを検証する、現場でそのまま使えるテンプレートだ。
import jwt
import requests
1. 認可サーバーのJWKSエンドポイントから公開鍵を取得する
本来はキャッシュすべきだが、ここでは概念を簡略化
jwks_client = jwt.PyJWKClient(“https://auth.example.com/.well-known/jwks.json”)
def verify_token(token):
try:
# 2. トークンのヘッダーからkey_idを取得し、適切な公開鍵を選択
signing_key = jwks_client.get_signing_key_from_jwt(token)
# 3. RS256アルゴリズムを明示的に指定して検証
# algorithms=[“RS256”] を指定しないと、攻撃者にalgを書き換えられるリスクがある
payload = jwt.decode(
token,
signing_key.key,
algorithms=[“RS256″],
audience=”my-api-service”
)
return payload
except jwt.exceptions.InvalidTokenError as e:
# ログには詳細を残すが、クライアントには汎用的なエラーを返す
print(f”セキュリティ警告: 不正なトークンが検知されました: {e}”)
return None
ここが重要: `algorithms=[“RS256”]` を必ず指定すること。これを省略すると、ライブラリがトークン内のアルゴリズムを信じてしまい、`none`攻撃の餌食になる。
—
4. インフラ側での防御:Nginxでの「JWT検証」
アプリ側の実装漏れをカバーするため、境界防御(WAFやAPI Gateway)でもチェックを入れるのが鉄則だ。Nginxを使うなら、`auth_request`モジュールを活用し、不正なトークンをアプリケーション層に到達させないのがベストプラクティスだ。
Nginx設定例:認証サブシステムへのリクエスト
location /api/ {
auth_request /auth-check;
# …
}
location = /auth-check {
internal;
proxy_pass http://auth-service/verify;
proxy_pass_request_body off; # ボディは検証不要
proxy_set_header Content-Length “”;
proxy_set_header X-Original-URI $request_uri;
}
—
最後に:セキュリティは「コスト」ではなく「信頼」
HS256を使いたくなるのは、鍵管理の設計コストをケチりたいという甘えだ。しかし、一度のトークン偽造による被害額を計算してみれば、RS256への移行にかかる工数など微々たるものだと気づくはずだ。
- 鍵のローテーション: JWKSを使えば、認証サーバー側で鍵を更新するだけで、サービスを止めずに鍵の入れ替えができる。
- 権限の分離: 認証する側と、サービスを提供する側を物理的に分ける。
これが、世界中のエンジニアが信頼を寄せる「安全な開発」の第一歩だ。今日のコードレビューから、`HS256`の文字を見つけたら、即座に修正を求めてほしい。それが君のシステムの信頼を守る唯一の道だ。

コメント