DevToolBox

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

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 — 秘密鍵で署名し、対になる公開鍵で検証する非対称方式です。

項目 / ItemHS256RS256
方式 / SchemeHMAC-SHA256(対称)RSASSA-PKCS1-v1_5 + SHA-256(非対称)
署名する鍵 / Signing key共有鍵(secret)RSA 秘密鍵
検証する鍵 / Verification key同じ共有鍵RSA 公開鍵(漏れても偽造不可)
検証鍵が漏れたら / If verifier key leaks誰でも偽造可能影響なし(公開前提)
署名サイズ / Signature size32 バイト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 鍵ペアの生成(2048bit 以上)
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

Count 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 点に集約されます。

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.

まとめ

JWT Generator

HS256 と RS256 で同じペイロードに署名して、署名サイズやヘッダーの違いを見比べてみましょう。生成した JWT はそのまま JWT Decoder で検証できます。

今すぐ試す →

関連ツール / Related tools

関連ガイド / Related guides