Saltar al contenido
ecdsa.com
Ruby

ruby-jwt (jwt gem)

ruby-jwt, la gema jwt que usan la mayoría de las aplicaciones Ruby y Rails: exige un algorithm explícito, comprueba los tipos de clave e incluye ES256K, pero EdDSA pasó a una gema aparte en la 3.x y los secretos HMAC cortos se aceptan sin aviso.
Licencia
MIT
Versión comprobada
3.3.0 ·
Código fuente
github.com/jwt/ruby-jwt
Documentación
github.com/jwt/ruby-jwt#readme

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

JWT.encode y JWT.decode cubren HS, RS, PS y ES de 256/384/512 y ES256K sobre la gema openssl de Ruby. EdDSA ya no forma parte de la gema jwt: con solo la gema jwt 3.3.0 cargada, codificar EdDSA falló con Unsupported signing method, y funcionó tras añadir jwt-eddsa, cuya dependencia nativa ed25519 necesita un compilador de C al instalarse, algo que no tienen las imágenes de contenedor slim.

La verificación exige indicar el algoritmo: JWT.decode(token, key, true) sin algorithm: falló con Expected a different algorithm. Los tokens sin firmar se rechazan al verificar y solo se pueden leer con la verificación desactivada (JWT.decode(token, nil, false)), algo que el código nunca debería hacer con los tokens en los que confía. Los tipos de clave se comprueban: un token HS256 falsificado con el texto PEM de la clave EC falló con HMAC key expected to be a String cuando la clave de verificación era el objeto de la clave EC, pero pasar la cadena PEM como clave HS256 lo verificó, así que conserva las claves como objetos OpenSSL::PKey. ES256 rechaza una clave P-384 al firmar.

Las comprobaciones de los claims hay que activarlas: sin tolerancia, y exp es opcional salvo que figure en required_claims, como hace el ejemplo junto con el emisor. Se aceptan secretos HMAC de cualquier longitud —una clave de 5 bytes firmó y verificó sin ninguna advertencia—, así que comprueba los secretos con el comprobador de secretos JWT.

Las firmas ES256 son 64 bytes de r‖s en bruto; una firma DER falla con Signature verification failed. Pega un token en el decodificador de JWT para ver qué codificación lleva.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/ruby-jwt/run.sh ejecutó ruby-jwt (jwt gem) 3.3.0 en la imagen de contenedor ruby:4.0.7-slim-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    Con HS, RS, PS y ES de 256/384/512 y ES256K se firmó y se verificó con la gema jwt; EdDSA, solo después de añadir la gema jwt-eddsa.

    Registrado por la comprobación
    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
  • Codificación de la firma ES256

    raw

    Las firmas ES256 son 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact jws-ecdsa-encoding = raw
  • Firma DER dentro de un JWS

    rejected

    Una firma ES256 codificada en DER falla con JWT::VerificationError: Signature verification failed.

    Registrado por la comprobación
    fact jws-der-signature = rejected
    evidence jws-der-signature: JWT::VerificationError: Signature verification failed
  • Lista de algoritmos permitidos

    required

    Verificar sin algorithm: falla con JWT::IncorrectAlgorithm: Expected a different algorithm.

    Registrado por la comprobación
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: JWT.decode with verify true and no algorithm option: JWT::IncorrectAlgorithm: Expected a different algorithm
  • Tokens sin firma (alg none)

    rejected

    La verificación de los tokens sin firmar falla con Expected a different algorithm; solo verify = false permite leerlos.

    Registrado por la comprobación
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: verify true, algorithm ES256: JWT::IncorrectAlgorithm: Expected a different algorithm; verify false: ok
  • Clave pública usada como secreto HMAC

    rejected

    Un token HS256 con el texto PEM como clave falló contra el objeto de la clave EC con HMAC key expected to be a String, pero se verificó cuando se pasó la propia cadena PEM como clave HS256.

    Registrado por la comprobación
    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
  • Curva cotejada con alg

    enforced

    Firmar ES256 con una clave P-384 falla con payload algorithm is ES256 but ES384 signing key was provided.

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: JWT::EncodeError: payload algorithm is ES256 but ES384 signing key was provided
  • Desfase de reloj predeterminado

    0s

    Sin tolerancia por defecto: se rechazó un token caducado 3 segundos antes.

    Registrado por la comprobación
    fact jwt-clock-skew = 0s
  • Claim de caducidad obligatorio

    no

    Un token sin exp se decodificó; required_claims: ["exp"] hace que falle con Missing required claim exp.

    Registrado por la comprobación
    fact jwt-exp-required = no
    evidence jwt-exp-required: with required_claims: ["exp"]: JWT::MissingRequiredClaim: Missing required claim exp
  • Secreto HMAC corto

    accepted

    Con un secreto HS256 de 5 bytes se firmó y se verificó sin ninguna advertencia.

    Registrado por la comprobación
    fact jwt-hmac-short-secret = accepted
    evidence jwt-hmac-short-secret: 5-byte HS256 key: ok

Uso

Codifica un token ES256 y decodifícalo indicando el algoritmo, con exp e iss obligatorios y 30 segundos de tolerancia.
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']}"

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor ruby:4.0.7-slim-trixie, y la ejecución falla si la salida no coincide.

Configuración
gem install --silent --no-document jwt -v 3.3.0
Comando
ruby usage.rb
La salida debe coincidir (expresión regular)
sub: user-42, alg: ES256

Bibliotecas