跳到主要内容
ecdsa.com

JWT vs PASETO:灵活的标准还是固定密码算法的令牌

PASETO 去掉了 alg 头部,也去掉了造成大多数 JWT 漏洞的那些选择。JWT 则保有生态系统:身份提供商、各类标准,以及每种语言的库。

在 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
算法选择取自 IANA 注册表的 alg 头部由版本和用途固定
公钥签名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 vs RS256)。RFC 8725 用一套配置规则修复了这个问题,所有现代库都支持这些规则,但每个部署都必须自己应用它们。

PASETO 用版本取代了协商。每个版本为每种用途固定一种原语:v4 对 public 令牌使用 Ed25519,对 local 令牌使用 XChaCha20 加密配合带密钥的 BLAKE2b MAC;v3 使用 NIST 算法(ECDSA P-384、AES-256-CTR 配合 HMAC-SHA-384),供需要这些算法的环境使用。为 v4 public 令牌配置的验证方无法被诱骗去接受其他任何东西;密钥也带有类型,因此 local 密钥不能当作公钥使用。预认证编码(PAE)把版本、用途、载荷、页脚(footer)以及可选的隐式断言绑定在一起;隐式断言例如租户标识符,双方都知道它,但令牌并不携带。

另有两点差异在实践中很重要。PASETO 的对称 local 令牌是加密的,而 JWT 的对称 HS256 令牌只计算了 MAC,内容可读;JWT 中与 v4.local 对应的是 JWE。此外,PASETO 没有与 JWKS 发现相对应的机制,密钥如何分发由你自己负责。

并排对比

下面的脚本在 Node.js 26.10 上用 paseto 3.1.4 和 jose 6.2.13 运行,用同一个 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 省掉的正是这一行。

生态系统与互操作性

JWT 已深度融入 OAuth 2.0(RFC 9068 访问令牌)、OpenID Connect ID 令牌、DPoP、SD-JWT、FAPI、云工作负载身份,以及几乎所有 API 网关和身份提供商。每种语言都有 JWT 库,而且经过了多年的安全审查。

PASETO 规范由 PASETO 标准项目在 GitHub 上维护;早先的 IETF 互联网草案已经过期,没有成为 RFC。许多语言都有 PASETO 库,但质量和版本覆盖参差不齐,网关和身份提供商很少接受 PASETO,也没有标准的发现机制。因此,选择 PASETO 就意味着颁发者、验证方和密钥分发都要由你自己负责。

如何选择

  • OAuth 或 OpenID Connect、第三方使用方、API 网关:JWT,并为每个密钥设置算法允许列表。
  • 你掌控的多语言系统中的内部服务令牌:两者皆可;如果每种语言都有持续维护的 v4 库,可以选 PASETO。
  • 由客户端保存的加密令牌(Cookie 中的会话数据):PASETO v4.local 比 JWE 简单;如果用 JWT,就使用 JWE,配合 dir 或 A256KW 以及 A256GCM。
  • 受 FIPS 约束的环境:使用 ES256 或 PS256 的 JWT,或者 PASETO v3;v4 所用的 XChaCha20 和 BLAKE2b 未获 FIPS 批准。
  • 刚接触令牌安全的团队:PASETO 消除了一整类配置错误;采用安全默认设置的 JWT 库(例如 jose,它要求算法与密钥类型匹配)可以缩小这一差距。

迁移说明

  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。

参考资料

全部对比