Перейти к содержимому
ecdsa.com
Ruby

ruby-jwt (jwt gem)

ruby-jwt — гем jwt, который используют большинство приложений на Ruby и Rails: явный algorithm обязателен, типы ключей проверяются, ES256K входит в состав — но EdDSA в 3.x переехал в отдельный гем, а короткие секреты HMAC принимаются без предупреждений.
Лицензия
MIT
Проверенная версия
3.3.0 ·
Исходный код
github.com/jwt/ruby-jwt
Документация
github.com/jwt/ruby-jwt#readme

Совместимость вкратце

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

JWT.encode и JWT.decode поддерживают HS, RS, PS и ES на 256/384/512, а также ES256K поверх гема openssl для Ruby. EdDSA больше не входит в гем jwt: когда был загружен только jwt 3.3.0, кодирование токена с EdDSA завершилось ошибкой Unsupported signing method, а после добавления jwt-eddsa заработало — но его нативной зависимости ed25519 при установке нужен компилятор C, которого нет в образах контейнеров slim.

Для проверки нужно назвать алгоритм: JWT.decode(token, key, true) без algorithm: завершился ошибкой Expected a different algorithm. Неподписанные токены при проверке отклоняются, а прочитать их можно только с отключённой проверкой (JWT.decode(token, nil, false)), чего никогда не следует делать с токенами, которым код доверяет. Типы ключей проверяются: токен HS256, подделанный с текстом PEM ключа EC, не прошёл проверку с ошибкой HMAC key expected to be a String, когда ключом проверки был объект ключа EC, — но если передать строку PEM как ключ HS256, токен проходил проверку, поэтому храните ключи как объекты OpenSSL::PKey. ES256 не принимает ключ P-384 уже при подписи.

Проверки утверждений нужно включать явно: допуска на расхождение часов нет, а exp необязателен, если не указать его в required_claims, как в примере, где обязательным вместе с ним сделан и издатель. Секреты HMAC принимаются любой длины (с ключом длиной 5 байт подпись и проверка прошли без предупреждения), поэтому проверяйте секреты инструментом проверки секретов JWT.

Подписи ES256 — это 64 байта в формате raw r‖s; подпись DER не проходит проверку с ошибкой Signature verification failed. Вставьте токен в отладчик JWT, чтобы увидеть, в какой кодировке его подпись.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/ruby-jwt/run.sh запустила ruby-jwt (jwt gem) 3.3.0 в образе контейнера ruby:4.0.7-slim-trixie; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    HS, RS, PS и ES на 256/384/512 и ES256K: подпись и проверка в геме jwt прошли успешно; EdDSA — только после добавления гема jwt-eddsa.

    Что записала проверка
    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 байта в формате raw r‖s.

    Что записала проверка
    fact jws-ecdsa-encoding = raw
  • Подпись в DER внутри JWS

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой 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

    Токен HS256, подписанный с текстом PEM в качестве ключа, не прошёл проверку с объектом ключа EC (HMAC key expected to be a String), но прошёл, когда в качестве ключа HS256 передали саму строку PEM.

    Что записала проверка
    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

    При подписи ES256 ключом P-384 возникает ошибка 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

    По умолчанию расхождение часов не допускается: токен, срок действия которого истёк 3 секунды назад, отклонён.

    Что записала проверка
    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

Библиотеки