本文へスキップ
ecdsa.com

JWT と PASETO の比較|柔軟な標準か暗号が固定のトークンか

PASETO は、JWT の脆弱性の大半を招いてきた alg ヘッダーとアルゴリズムの選択肢をなくしました。一方 JWT には、ID プロバイダー、各種の標準、あらゆる言語のライブラリというエコシステムがあります。

JWT と PASETO のどちらを選ぶかは、柔軟性と誤用への耐性のトレードオフです。JWT(RFC 7519、通常は JWS)では、長いレジストリの中からトークン自身がアルゴリズムを指定できるため、alg: none や RS256 と HS256 の混同といった典型的なバグが起こり得ました。PASETO(Platform-Agnostic SEcurity TOkens)はプロトコルバージョンごとに暗号方式を固定しているため、v4 の public トークンは常に Ed25519、v4 の local トークンは常に XChaCha20 と BLAKE2b の組み合わせです。

比較表

JWT(JWS)PASETO v4
仕様RFC 7519 と RFC 7515/7518(IETF の標準)コミュニティによる仕様(paseto-standard/paseto-spec)。IETF のドラフトは期限切れ
形式base64url(header).base64url(claims).base64url(signature)v4.public. + base64url(メッセージ ‖ 署名)、省略可能な .footer
アルゴリズムの選択alg ヘッダーで IANA のレジストリから指定バージョンと用途で固定
公開鍵による署名ES256、RS256、PS256、EdDSA/Ed25519 などv4.public:Ed25519 のみ(v3.public:ECDSA P-384)
共通鍵方式のトークンHS256(MAC のみで、クレームは読める状態)または JWEv4.local:XChaCha20 + BLAKE2b-MAC(暗号化と認証の両方)
none とアルゴリズム混同検証者の設定を誤ると起こり得るそもそも表現できない
暗黙のアサーション(認証されるが送信されない)なしあり(v3 と v4)
例のサイズ(後述の実測値)227 文字280 文字
鍵の形式JWK と JWKSPASERK(k4.public…、k4.local…)
エコシステムあらゆる IdP、OAuth/OIDC、ゲートウェイ、クラウドサービス多くの言語にライブラリがある。IdP やゲートウェイの対応はわずか

設計上の違い

JWT の検証者は、アルゴリズムを指定するヘッダーを受け取り、それを信頼してよいかを判断しなければなりません。RFC 7518 には十数種類の署名アルゴリズムと MAC アルゴリズムに加えて none が登録されており、ライブラリはかつてヘッダーの値に従って処理を振り分けていました。これが 2015年に相次いだバグの原因です。署名のないトークンが受け入れられ、RSA の公開鍵が HMAC のシークレットとして使われました(HS256 と RS256 の比較を参照)。RFC 8725 は、最近のライブラリがすべて対応している設定ルールによってこの問題を解決していますが、そのルールはデプロイごとに適用しなければなりません。

PASETO は、ネゴシエーションの代わりにバージョンを使います。各バージョンは用途ごとにプリミティブを 1 つに固定しています。v4 は public トークンに Ed25519 を使い、local トークンには XChaCha20 による暗号化と鍵付き BLAKE2b による MAC を使います。v3 は、NIST のアルゴリズムが求められる環境向けに、それらのアルゴリズム(ECDSA P-384、HMAC-SHA-384 と組み合わせた AES-256-CTR)を使います。v4 の public トークン用に設定した検証者を欺いて、それ以外の方式を受け入れさせることはできません。また鍵には型があるため、local 用の鍵を公開鍵として使うこともできません。事前認証エンコーディング(PAE)は、バージョン、用途、ペイロード、フッター、そして省略可能な暗黙のアサーションを結び付けます。暗黙のアサーションとは、たとえば双方が知っていてもトークンには含まれないテナント識別子のような値です。

実務では、ほかに 2 つの違いが重要です。PASETO の共通鍵方式の local トークンは暗号化されますが、JWT の共通鍵方式の HS256 トークンは MAC が付くだけで、中身は誰でも読めます。v4.local に相当する JWT は JWE です。また、PASETO には JWKS のディスカバリに相当する仕組みがないため、鍵の配布は自分で用意する必要があります。

並べて比較

次のスクリプトは Node.js 26.10 上の paseto 3.1.4 と jose 6.2.13 で実行したもので、1 つの Ed25519 鍵を使い、同じクレームを両方の形式で発行します。

同じ Ed25519 鍵とクレームを PASETO v4.public と JWT で発行
import { generateKeyPairSync } from "node:crypto";
import { SignJWT, jwtVerify } from "jose";
import { V4 } from "paseto";

const claims = { sub: "user-42", scope: "orders:read" };
const { privateKey, publicKey } = generateKeyPairSync("ed25519");

// PASETO v4.public: Ed25519 is implied by the version; no alg header exists.
const paseto = await V4.sign(claims, privateKey, { expiresIn: "15m", issuer: "https://auth.example.com" });
const pasetoClaims = await V4.verify(paseto, publicKey, { issuer: "https://auth.example.com" });

// JWT with the same key: the verifier must pin the algorithm itself.
const jwt = await new SignJWT(claims).setProtectedHeader({ alg: "EdDSA" }).setIssuer("https://auth.example.com").setExpirationTime("15m").sign(privateKey);
const { payload } = await jwtVerify(jwt, publicKey, { algorithms: ["EdDSA"], issuer: "https://auth.example.com" });

console.log("PASETO prefix:", paseto.slice(0, 10), "length:", paseto.length, "sub:", pasetoClaims.sub);
console.log("JWT    prefix:", jwt.slice(0, 10), "length:", jwt.length, "sub:", payload.sub);
出力
PASETO prefix: v4.public. length: 280 sub: user-42
JWT    prefix: eyJhbGciOi length: 227 sub: user-42

ここで PASETO のほうが少し長いのは、ライブラリが iat を追加し、ISO 8601 形式のタイムスタンプを使うためです。JWT は日時を数値で表します。署名のサイズは同じです。JWT 側の algorithms: ["EdDSA"] オプションに注目してください。PASETO では、この 1 行が不要になります。

エコシステムと相互運用性

JWT は、OAuth 2.0(RFC 9068 のアクセストークン)、OpenID Connect の ID トークン、DPoP、SD-JWT、FAPI、クラウドのワークロード ID、そして事実上すべての API ゲートウェイと ID プロバイダーに組み込まれています。ライブラリはあらゆる言語にあり、長年にわたってセキュリティレビューを受けてきました。

PASETO の仕様は、PASETO の標準化プロジェクトが GitHub で管理しています。以前の IETF のインターネットドラフトは、RFC にならないまま期限切れになりました。ライブラリは多くの言語にありますが、品質や対応バージョンはまちまちで、PASETO を受け付けるゲートウェイや ID プロバイダーはほとんどなく、標準のディスカバリの仕組みもありません。したがって PASETO を選ぶということは、発行者、検証者、鍵の配布をすべて自分で担うということです。

どちらを選ぶべきか

  • OAuth や OpenID Connect、第三者の利用者、API ゲートウェイ:JWT を使い、鍵ごとにアルゴリズムの許可リストを設定します。
  • 自社で管理する、複数の言語で構成されたシステムの内部サービス用トークン:どちらでもかまいません。すべての言語でメンテナンスされている v4 のライブラリがあるなら PASETO を使います。
  • クライアントが保存する暗号化トークン(Cookie に入れるセッションデータ):PASETO の v4.local のほうが JWE より簡単です。JWT を使う場合は、dir または A256KW に A256GCM を組み合わせた JWE を使います。
  • FIPS の制約がある環境:ES256 または PS256 の JWT か、PASETO v3 を使います。v4 の XChaCha20 と BLAKE2b は FIPS で承認されていません。
  • トークンのセキュリティに不慣れなチーム:PASETO では、ある種類の設定エラーがまるごとなくなります。安全なデフォルトを持つ JWT ライブラリ(アルゴリズムが鍵の種類と一致することを求める jose など)を使えば、その差は縮まります。

移行時の注意点

  1. 1 つの検証者で形式を混在させない:プレフィックス(v4.public. か eyJ か)で振り分け、別々の鍵を使う別々の検証コードパスに渡します。
  2. クレームを明示的に対応付ける:PASETO は iss、sub、aud、exp、nbf、iat、jti、kid を予約しており、日時は ISO 8601 形式です。JWT は NumericDate(秒)を使います。
  3. JWKS の代わりに独自の鍵配布を用意します(PASERK 文字列、構成管理、鍵管理サービスなど)。PASETO のフッターに入れた kid による鍵のローテーションも含めて設計してください。
  4. 外部との境界には JWT を残す:外部の相手には JWT を発行し、内部では PASETO を使うシステムも多くあります。
  5. JWT を使い続けるなら、今すぐ RFC 8725 を適用してください。アルゴリズムを固定し、typ、iss、aud を検証し、256 ビットの HMAC シークレットか非対称鍵を使い、none を拒否します。

参考資料

すべての比較