跳到主要内容
ecdsa.com
Ruby

ruby-jwt (jwt gem)

ruby-jwt,即大多数 Ruby 和 Rails 应用使用的 jwt gem:必须显式指定 algorithm,会检查密钥类型,包含 ES256K——但 EdDSA 在 3.x 中已移到单独的 gem,而且过短的 HMAC 密钥会被悄无声息地接受。
许可证
MIT
检查的版本
3.3.0 ·
源代码
github.com/jwt/ruby-jwt
文档
github.com/jwt/ruby-jwt#readme

兼容性概览

JOSE 与 JWT

JWS 算法
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

JWT.encode 和 JWT.decode 基于 Ruby 的 openssl gem,支持 256/384/512 的 HS、RS、PS 和 ES 以及 ES256K。EdDSA 已不再属于 jwt gem:只加载 jwt 3.3.0 时,用 EdDSA 编码报错 Unsupported signing method,添加 jwt-eddsa 后才能正常工作——但它的原生依赖 ed25519 在安装时需要 C 编译器,而 slim 容器镜像中没有。

验证时必须指明算法:不带 algorithm: 调用 JWT.decode(token, key, true) 报错 Expected a different algorithm。未签名的令牌在验证时会被拒绝,只有关闭验证(JWT.decode(token, nil, false))才能读取,而对于要信任的令牌,代码绝不应这样做。密钥类型会被检查:用 EC 密钥的 PEM 文本伪造的 HS256 令牌,在验证密钥为 EC 密钥对象时报错 HMAC key expected to be a String——但把 PEM 字符串作为 HS256 密钥传入时,它验证通过了,因此要把密钥保存为 OpenSSL::PKey 对象。ES256 在签名时会拒绝 P-384 密钥。

声明检查需要显式启用:没有容差,而且除非列入 required_claims,否则 exp 是可选的——示例就把它和颁发者一起列了进去。任意长度的 HMAC 密钥都会被接受——5 字节的密钥签名和验证都没有任何警告——因此要用 JWT 密钥检测工具检查密钥。

ES256 签名是 64 字节的原始 r‖s;DER 签名会报错 Signature verification failed。把令牌粘贴到 JWT 调试器中,即可查看它采用哪种编码。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/ruby-jwt/run.sh 于2026年10月20日在容器镜像 ruby:4.0.7-slim-trixie 中运行了 ruby-jwt (jwt gem) 3.3.0;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • JWS 算法

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    使用 jwt gem 时,256/384/512 的 HS、RS、PS 和 ES 以及 ES256K 的签名与验证均成功;EdDSA 只有在添加 jwt-eddsa gem 后才可用。

    检查记录
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA
    evidence jws-algorithms: EdDSA with only the jwt gem loaded: JWT::EncodeError: Unsupported signing method; after require "jwt/eddsa": ok
  • ES256 签名编码

    raw

    ES256 签名是 64 字节的原始 r‖s。

    检查记录
    fact jws-ecdsa-encoding = raw
  • JWS 中的 DER 签名

    rejected

    DER 编码的 ES256 签名会报错 JWT::VerificationError: Signature verification failed。

    检查记录
    fact jws-der-signature = rejected
    evidence jws-der-signature: JWT::VerificationError: Signature verification failed
  • 算法允许列表

    required

    不带 algorithm: 验证时报错 JWT::IncorrectAlgorithm: Expected a different algorithm。

    检查记录
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: JWT.decode with verify true and no algorithm option: JWT::IncorrectAlgorithm: Expected a different algorithm
  • 未签名的令牌(alg none)

    rejected

    未签名的令牌验证失败,报错 Expected a different algorithm;只有 verify = false 才能读取它们。

    检查记录
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: verify true, algorithm ES256: JWT::IncorrectAlgorithm: Expected a different algorithm; verify false: ok
  • 把公钥用作 HMAC 密钥

    rejected

    以 PEM 文本为密钥的 HS256 令牌,用 EC 密钥对象验证时报错 HMAC key expected to be a String,但直接把 PEM 字符串作为 HS256 密钥传入时验证通过。

    检查记录
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: algorithms [ES256, HS256] with the EC key object: JWT::VerificationKeyError: HMAC key expected to be a String
    evidence jwt-hmac-public-key: algorithm HS256 with the PEM string as key: ok
  • 按 alg 检查曲线

    enforced

    用 P-384 密钥签名 ES256 时报错 payload algorithm is ES256 but ES384 signing key was provided。

    检查记录
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: JWT::EncodeError: payload algorithm is ES256 but ES384 signing key was provided
  • 默认时钟偏差

    0s

    默认没有容差:三秒前过期的令牌被拒绝。

    检查记录
    fact jwt-clock-skew = 0s
  • 是否要求过期时间声明

    no

    不含 exp 的令牌能被解码;设置 required_claims: ["exp"] 后,它会报错 Missing required claim exp。

    检查记录
    fact jwt-exp-required = no
    evidence jwt-exp-required: with required_claims: ["exp"]: JWT::MissingRequiredClaim: Missing required claim exp
  • 过短的 HMAC 密钥

    accepted

    5 字节的 HS256 密钥签名和验证均成功,没有任何警告。

    检查记录
    fact jwt-hmac-short-secret = accepted
    evidence jwt-hmac-short-secret: 5-byte HS256 key: ok

用法

编码 ES256 令牌,然后在指明算法、要求 exp 和 iss 并留出 30 秒容差的条件下解码。
require "jwt"

key = OpenSSL::PKey::EC.generate("prime256v1")
public_key = OpenSSL::PKey.read(key.public_to_pem)
payload = { "sub" => "user-42", "iss" => "https://auth.example.com", "exp" => Time.now.to_i + 600 }
token = JWT.encode(payload, key, "ES256")

# Name the algorithm, require exp and iss, and allow some clock drift.
claims, header = JWT.decode(token, public_key, true,
                            algorithm: "ES256",
                            required_claims: %w[exp iss],
                            iss: "https://auth.example.com", verify_iss: true,
                            leeway: 30)
puts "sub: #{claims['sub']}, alg: #{header['alg']}"

代码片段测试工具会在容器镜像 ruby:4.0.7-slim-trixie 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
gem install --silent --no-document jwt -v 3.3.0
命令
ruby usage.rb
输出必须匹配(正则表达式)
sub: user-42, alg: ES256

库