JWT の HS256 と RS256 の違い - どちらを選ぶべきか
JWT の署名アルゴリズムで最初に迷うのが HS256(共有鍵 HMAC)とRS256(公開鍵 RSA)の選択です。結論はシンプルで、発行者と検証者が同じなら HS256、検証側が複数・外部にあるなら RS256。 本記事では仕組みと鍵管理の違い、マイクロサービスでの検証パターン、そしてalg=none 攻撃・alg 混同攻撃(公開鍵を HMAC 鍵として悪用)の防御までを実装コードつきで解説します。
HS256 signs JWTs with a shared HMAC secret; RS256 signs with an RSA private key and verifies with the public key. This guide explains how to choose between them, how key distribution works in microservices (JWKS, kid), and how to defend against the alg=none and algorithm-confusion attacks.
TL;DR
- HS256: 署名も検証も同じ共有鍵。発行者=検証者の単一サービス向け。速くて運用が簡単
- RS256: 秘密鍵で署名、公開鍵で検証。検証側に渡すのは公開鍵だけなので漏れても偽造不可
- マイクロサービス・サードパーティ検証・OIDC は RS256 一択(公開鍵は JWKS で配布)
- RSA 鍵は 2048 ビット以上(RFC 7518 で必須)。HS256 の共有鍵は 32 バイト以上のランダム値
- 検証時は必ず
algorithms: ["RS256"]のように許可リストを固定 — alg=none / alg 混同攻撃の防御線 - トークンのヘッダーを見れば現在の alg がわかる(
{"alg":"HS256","typ":"JWT"})
JWT Generator
HS256 / RS256 を切り替えて JWT をその場で生成。ヘッダー・ペイロード・署名がどう変わるかをブラウザ内で確認できます。鍵やトークンは外部送信されません。
今すぐ試す →1. 仕組みの違い / How HS256 and RS256 work
どちらも RFC 7518(JSON Web Algorithms)で定義された署名方式です。HS256 は HMAC-SHA256 — メッセージ認証コードなので、署名の生成と検証にまったく同じ共有鍵を使います。RS256 は RSASSA-PKCS1-v1_5 + SHA-256 — 秘密鍵で署名し、対になる公開鍵で検証する非対称方式です。
| 項目 / Item | HS256 | RS256 |
|---|---|---|
| 方式 / Scheme | HMAC-SHA256(対称) | RSASSA-PKCS1-v1_5 + SHA-256(非対称) |
| 署名する鍵 / Signing key | 共有鍵(secret) | RSA 秘密鍵 |
| 検証する鍵 / Verification key | 同じ共有鍵 | RSA 公開鍵(漏れても偽造不可) |
| 検証鍵が漏れたら / If verifier key leaks | 誰でも偽造可能 | 影響なし(公開前提) |
| 署名サイズ / Signature size | 32 バイト | 256 バイト(2048bit 鍵) |
| 向いている構成 / Best fit | 発行者=検証者の単一サービス | マイクロサービス・外部検証・OIDC |
HS256 is symmetric: one shared secret signs and verifies, so anyone who can verify can also forge. RS256 is asymmetric: the private key signs, the public key verifies, and leaking the public key is harmless.
2. 選び方 - 鍵管理が決め手 / How to choose: key management decides
判断基準は「トークンを検証する場所がいくつあるか」です。 モノリスや BFF のように発行と検証が同じプロセス・同じチームに閉じるなら HS256 で十分。 共有鍵 1 本を環境変数で管理するだけで済みます。
一方、HS256 のままマイクロサービスに広げると、検証する全サービスに共有鍵を配ることになります。 これは「どのサービスが漏らしても全体のトークンを偽造できる」だけでなく、 「検証しかしないはずのサービスがトークンを発行できてしまう」ことを意味します。 RS256 なら検証側に渡すのは公開鍵だけ。発行権限は秘密鍵を持つ認証サービスに閉じられます。
- RSA 鍵は 2048 ビット以上が必須(RFC 7518 Section 3.3)
- 公開鍵は
/.well-known/jwks.json(JWKS)で配布し、ヘッダーのkidで鍵を特定する - 鍵ローテーションは「新 kid で発行開始 → 旧 kid を JWKS に残したまま検証 → 旧トークン失効後に削除」の順
- OpenID Connect の ID トークンは RS256 がデフォルト。Auth0・Firebase Auth・Cognito も RS256 + JWKS 方式
# RSA 鍵ペアの生成(2048bit 以上)
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pemCount your verifiers. One service: HS256 with a single env-var secret is fine. Multiple services or third parties: RS256 — distribute only the public key via a JWKS endpoint, rotate keys with the kid header, and keep issuing power locked to the auth service that holds the private key.
3. Node.js での実装例 / Implementation in Node.js
jsonwebtoken での最小実装です。ポイントは検証時に algorithms を必ず明示すること(理由は次章)。
const jwt = require("jsonwebtoken");
const fs = require("fs");
// --- HS256: 同じ共有鍵で署名・検証 ---
const token = jwt.sign({ sub: "user_123" }, process.env.JWT_SECRET, {
algorithm: "HS256",
expiresIn: "1h",
});
const payload = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ["HS256"], // 許可リストを固定
});
// --- RS256: 秘密鍵で署名、公開鍵で検証 ---
const privateKey = fs.readFileSync("private.pem");
const publicKey = fs.readFileSync("public.pem");
const token2 = jwt.sign({ sub: "user_123" }, privateKey, {
algorithm: "RS256",
expiresIn: "1h",
keyid: "2026-06-key1", // ヘッダーの kid になる
});
const payload2 = jwt.verify(token2, publicKey, {
algorithms: ["RS256"],
});鍵やアルゴリズムが合わないと JsonWebTokenError: invalid signature、 トークンの alg が許可リスト外だと JsonWebTokenError: invalid algorithm が投げられます。 この 2 つのエラーの切り分けはJWT 署名検証失敗ガイドで詳しく扱っています。
With jsonwebtoken, always pass an explicit algorithms allowlist to verify(). A key mismatch throws "invalid signature"; a token whose alg is outside the allowlist throws "invalid algorithm".
4. alg=none 攻撃と alg 混同攻撃 / alg=none and algorithm confusion attacks
JWT のヘッダーには alg フィールドがあり、攻撃者が自由に書き換えられるという前提を忘れると 2 つの古典的攻撃が成立します。どちらも 2015 年に Tim McLean 氏が 複数の JWT ライブラリに対して報告した実在の脆弱性パターンです。
(1) alg=none 攻撃 — RFC 7519 には署名なしの「Unsecured JWT」 (ヘッダー {"alg":"none"}、署名部が空)が定義されています。 検証コードがトークンのヘッダーから alg を読んで処理を分岐すると、署名検証そのものがスキップされ、 ペイロードを改ざんしたトークンが素通りします。
(2) alg 混同攻撃(RS256 → HS256) — RS256 の公開鍵は秘密ではありません。 攻撃者はヘッダーを HS256 に書き換え、公開鍵の PEM 文字列そのものを HMAC の共有鍵として署名します。検証側が jwt.verify(token, publicKeyPem) と algorithms 未指定で呼んでいると、ライブラリは「HS256 のトークンを publicKeyPem という鍵で検証」 してしまい、攻撃者のトークンが正規署名として通ります。
// NG: alg をトークン任せにしている(混同攻撃が成立しうる)
jwt.verify(token, publicKey);
// OK: アルゴリズムを固定する
jwt.verify(token, publicKey, { algorithms: ["RS256"] });防御は次の 3 点に集約されます。
- algorithms 許可リストを常に明示し、トークンのヘッダーで分岐しない
- ライブラリを最新に保つ。jsonwebtoken v9 以降は鍵タイプも検証し、RSA 公開鍵を HS256 で使おうとすると
secretOrPublicKey must be a symmetric key when using HS256で拒否される - jose(panva)では許可外 alg は
JOSEAlgNotAllowed: "alg" (algorithm) Header Parameter value not allowedとして拒否される。HS256 と RS256 の鍵を同じ変数で 使い回さない設計にする
The alg header is attacker-controlled. alg=none strips the signature entirely; algorithm confusion re-signs the token with HS256 using the public RSA key PEM as the HMAC secret. Pin the algorithms allowlist, never branch on the token header, and use library versions that validate key types.
English summary
HS256 and RS256 are the two most common JWT signing algorithms defined in RFC 7518. HS256 is HMAC-SHA256, a symmetric scheme: the same shared secret both signs and verifies, so every service that can verify a token can also forge one. RS256 is RSASSA-PKCS1-v1_5 with SHA-256, an asymmetric scheme: the issuer signs with an RSA private key (2048 bits or larger is mandatory) and verifiers only need the public key, which is safe to distribute — typically via a JWKS endpoint at /.well-known/jwks.json, with the kid header selecting the right key during rotation. The decision rule is the number of trust boundaries: a single service that issues and verifies its own tokens can use HS256 with one well-protected random secret of at least 32 bytes; microservices, third-party APIs, and OpenID Connect (where RS256 is the default for ID tokens) should use RS256 so that issuing power stays locked to the auth service. Two classic attacks exploit the attacker-controlled alg header. The alg=none attack submits an Unsecured JWT with an empty signature; if the verifier trusts the header, signature checking is skipped. The algorithm-confusion attack rewrites alg to HS256 and signs the token with the public-key PEM string as the HMAC secret, which passes when code calls verify(token, publicKey) without an algorithms option. Defend by always pinning an explicit algorithms allowlist, never deriving the algorithm from the token, and using modern libraries: jsonwebtoken v9+ rejects asymmetric keys for HMAC algorithms and jose throws JOSEAlgNotAllowed for disallowed algorithms.
まとめ
- 発行者=検証者なら HS256、検証側が複数・外部なら RS256。迷ったら検証箇所の数で決める
- RS256 は公開鍵を JWKS で配布し、
kidでローテーション。発行権限は秘密鍵を持つサービスだけに残る - 検証コードでは
algorithms許可リストの明示が必須 — alg=none と alg 混同攻撃の両方をこれ 1 つで断てる
JWT Generator
HS256 と RS256 で同じペイロードに署名して、署名サイズやヘッダーの違いを見比べてみましょう。生成した JWT はそのまま JWT Decoder で検証できます。
今すぐ試す →関連ツール / Related tools
- JWT Generator — HS256 / RS256 で JWT を生成
- JWT Decoder — ヘッダーの alg・kid とペイロードを即確認
- Base64 Encoder / Decoder