OIDC の id_token 署名アルゴリズムの選び方 — RS256 / ES256 / HS256 の違いと、選定より効く 1 行
自作の OpenID Connect プロバイダーを運用していて、id_token の署名アルゴリズムをどれにするか改めて調べた。RS256 と HS256 の2つしか知られていないことが多いが、JWA (RFC 7518) には他にもいくつか定義されている。
先に結論を書くと、RS256 か ES256 を使えばいい。HS256 は避ける。ただ、調べていて感じたのは、どれを選ぶかよりも「受け入れる alg を固定しているか」のほうが桁違いに重要だということだ。
選べるアルゴリズム
| alg | 正式名 | 種別 | 鍵 | 署名長 |
|---|---|---|---|---|
| HS256/384/512 | HMAC with SHA-2 | 対称 | 共有秘密 | 32/48/64 B |
| RS256/384/512 | RSASSA-PKCS1-v1_5 | 非対称 | RSA 2048bit 以上 | 256 B (2048bit 時) |
| PS256/384/512 | RSASSA-PSS | 非対称 | RSA 2048bit 以上 | 256 B |
| ES256 | ECDSA P-256 + SHA-256 | 非対称 | EC P-256 | 64 B |
| ES384 / ES512 | ECDSA P-384 / P-521 | 非対称 | EC | 96 / 132 B |
| ES256K | ECDSA secp256k1 (RFC 8812) | 非対称 | secp256k1 | 64 B |
| Ed25519 / Ed448 | EdDSA (RFC 8037 → RFC 9864) | 非対称 | Ed25519 等 | 64 B |
| none | 署名なし | — | — | 0 |
EdDSA は 2025 年に RFC 9864 (Fully-Specified Algorithms for JOSE and COSE) が出て、EdDSA という識別子だけでは Ed25519 と Ed448 のどちらなのか判別できず、OIDC Discovery や WebAuthn のネゴシエーションで実害が出ていたため、曲線を特定した Ed25519 / Ed448 という識別子に移行した。古い EdDSA は Deprecated になっている。
なお id_token を暗号化する場合 (id_token_encrypted_response_alg) は JWE の別系統 (RSA-OAEP / ECDH-ES / A128KW など) で、署名とは分けて考える。
一番大きい違いは対称か非対称か
| HS256 (対称) | RS256 / ES256 (非対称) | |
|---|---|---|
| 鍵 | OP と RP が同じ鍵を共有 | OP が秘密鍵、RP は jwks_uri から公開鍵を取得 |
| 偽造 | 検証できる者は偽造もできる | RP は検証のみ |
| RP が複数 | RP ごとに別の鍵。鍵を公開できない | 公開鍵 1 組で全 RP をまかなえる |
| SPA / モバイル | secret を持てないので使えない | 使える |
| 鍵ローテーション | 再発行のたびに全 RP と調整 | kid 付きで JWKS を差し替えるだけ |
| 鍵強度 | 共有秘密のエントロピー | RSA 2048 / P-256 の強度 |
実務で一番効くのは最後の行だ。
OIDC で HS256 を使う場合、HMAC の鍵は client_secret そのものになる。RFC 7518 は HMAC 鍵をハッシュ出力長以上 (HS256 なら 256bit 以上) にしろと書いているが、現実の client_secret は管理画面で発行された短い文字列だったり、人間が決めた文字列だったりする。
そうなると、JWT を 1 個キャプチャすればオフラインで secret を総当たりできる。hashcat には JWT 用のモード (-m 16500) があって、辞書攻撃がそのまま通る。ネットワーク越しの試行ではないので、レート制限も効かない。
HS256 というアルゴリズムが弱いわけではない。HMAC-SHA256 自体は堅牢だ。弱いのは、その鍵として client_secret を使う運用のほうだ。
アルゴリズムの強度差は選定理由にならない
意外だったが、アルゴリズム自体の暗号強度は選定の主要因にならない。
HS256 / RS256 (2048bit) / ES256 / Ed25519 は、どれも実用上「破れない」水準にある。厳密には RSA-2048 は約 112bit 相当で、この中では最も低い。NIST は 2030 年までは容認、以降は 3072bit 以上を推奨している。P-256 と Ed25519 は 128bit 相当だ。SHA-256 の衝突耐性は 128bit だが、署名検証で効くのは第二原像耐性なので実害はない。
つまり「RS256 より ES256 のほうが安全か」という問いには、ほとんど意味がない。選定理由になるのは鍵管理のしやすさとトークンサイズであって、強度ではない。
本当の脅威はネゴシエーションにある
JWT の実装脆弱性は、暗号そのものではなくヘッダの解釈に集中している。
alg: none
ヘッダを {"alg":"none"} にして署名部を空にする。検証器が素直に「ヘッダの指定通り署名なしとして扱う」実装だと通る。
alg confusion (RS256 → HS256)
JWT で最も有名な攻撃で、これが一番重要だ。
- OP は jwks_uri で公開鍵を誰でも取れるように公開している (これ自体は正しい設計)
- 攻撃者はその公開鍵を取得する
- ヘッダの alg を RS256 から HS256 に書き換える
- 公開鍵の生バイト列を HMAC の鍵としてトークンに署名する
- 検証器が「ヘッダの alg を見て、設定された鍵で検証する」実装だと、HS256 として同じ公開鍵で MAC を再計算し、一致してしまう
公開されている値が、そのまま署名鍵として通ってしまう。根本原因は、攻撃者が制御できるヘッダの値を見て検証方法を決めていることだ。
jku / x5u injection
JWS ヘッダには鍵の取得先 URL を書けるフィールドがある。検証器がこれを素直に fetch すると、攻撃者が用意した JWKS で検証させられる。まともなライブラリはヘッダ由来の URL を参照しない。
ECDSA の nonce 再利用
ECDSA は署名ごとに乱数 k を使う。これが 2 回重複すると秘密鍵が復元できる。PS3 の署名鍵が漏れた事件がこれだ。RFC 6979 の決定的 ECDSA を使えば回避できる。EdDSA は設計上そもそも決定的なので、この問題がない。EdDSA の実装上の強みのひとつだ。
対策は期待する alg を固定するだけ
上の alg: none と alg confusion は、同じ 1 行で消える。トークンのヘッダに書かれた alg を信用せず、自分が期待する alg のみを許可する。
OIDC ではクライアント登録メタデータの id_token_signed_response_alg で「この RP はこの alg しか受け取らない」を宣言できる。それを検証側にも反映する。
// ASP.NET Core (Microsoft.IdentityModel)
o.TokenValidationParameters.ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 }; // "RS256"
# PyJWT: algorithms は必須引数なので構造的に忘れられない
jwt.decode(token, key, algorithms=["RS256"])
// node-jsonwebtoken: 省略すると危険。必ず明示する
jwt.verify(token, key, { algorithms: ['RS256'] })
ライブラリを選ぶときは、期待アルゴリズムの指定が必須引数になっているかを見るといい。省略できる API は、省略された瞬間に脆弱になる。
選定表
| alg | 評価 |
|---|---|
| RS256 | 相互運用の最大公約数。OIDC Core が OP に実装を必須と定めている唯一の署名アルゴリズムなので必ず使える。迷ったらこれ |
| ES256 | 現代的な第一候補。署名が 64 B と RSA-2048 の 1/4 で、トークンが小さい。鍵も小さく、署名生成が速い |
| PS256 | RSASSA-PSS は PKCS#1 v1.5 より理論的に堅牢 (安全性証明がある)。ただし対応していない実装がある。強く要求するほどの差ではない |
| Ed25519 | 暗号学的にはベスト。決定的で nonce 事故がなく、高速で小さい。ただし言語やランタイムによっては標準ライブラリにない (.NET は BCL 未対応で BouncyCastle が要る) |
| HS256 | 避ける。署名強度が client_secret のエントロピーに落ちる。鍵ローテーションが全 RP との調整を伴う。パブリッククライアントで使えない |
| none | 論外 |
補足として、署名の検証速度は RSA (公開指数 65537) のほうが ECDSA より速い。RP は検証しかしないので、RS256 を選ぶ理由にはなっても弱点にはならない。ES256 を選ぶ理由はトークンサイズと鍵管理であって、性能ではない。
参考
- RFC 7518 — JSON Web Algorithms (JWA)
- RFC 8037 — CFRG ECDH and Signatures in JOSE
- RFC 8812 — CBOR/JOSE Registrations for WebAuthn Algorithms (ES256K)
- RFC 9864 — Fully-Specified Algorithms for JOSE and COSE
- OpenID Connect Core 1.0 §15.1 (Mandatory to Implement Features)
開発相談をお待ちしています。