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

jose4j

jose4j Брайана Кэмпбелла — библиотека JOSE, на которой построены несколько поставщиков удостоверений на Java: JwtConsumer по умолчанию блокирует none и отклоняет ключи неподходящего типа, но допускает для ключа любой другой алгоритм, если вы его не ограничите.
Лицензия
Apache-2.0
Проверенная версия
0.9.7 ·
Исходный код
bitbucket.org/b_c/jose4j
Документация
bitbucket.org/b_c/jose4j/wiki/Home

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

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

jose4j реализует JWS, JWE, JWK и JWT на собственных провайдерах JDK: для подписи служит JsonWebSignature, а для проверки подписи и утверждений — JwtConsumer, созданный через JwtConsumerBuilder. В версии 0.9.7 подпись и проверка работали для HS, RS, PS и ES в вариантах 256/384/512 и для EdDSA; ES256K, Ed25519 и Ed448 библиотека не распознаёт как имена алгоритмов.

Настройки по умолчанию блокируют две классические атаки, но оставляют алгоритм открытым. none отклоняется ('none' is a blocked algorithm.), если явно не отключить требование подписи, а тип ключа должен подходить к алгоритму: токен RS256 при проверке ключом EC и токен HS256, подписанный текстом PEM ключа EC, не прошли проверку (cannot be used for HMAC verification). Однако любой другой алгоритм, который может выполнить ключ, принимается, поэтому зафиксируйте алгоритм через setJwsAlgorithmConstraints(ConstraintType.PERMIT, ...), как это делает пример. С HmacKey, созданным из байтов PEM, поддельный токен HS256 прошёл проверку: ключи должны браться из конфигурации и никогда — из того, что заявляет токен.

Размеры ключей проверяются: ES256 не принимает ключ P-384 (expects a key using P-256 but was P-384), а HS256 отклонил 16-байтовый ключ, потому что RFC 7518 требует ключей не короче выхода хеш-функции. Подписи ES256 — это 64-байтовый формат raw r‖s; подпись DER не проходит проверку с ошибкой Invalid JWS Signature, а её длину покажет отладчик JWT.

Проверка утверждений по умолчанию строга в одном и мягка в другом: допуска на расхождение часов нет, и это строже, чем ожидает большинство команд, а exp необязателен, и это, наоборот, мягче ожидаемого. setRequireExpirationTime(), setExpectedIssuer(), setExpectedAudience() и setAllowedClockSkewInSeconds() должны быть в каждом JwtConsumer, который читает токены другого сервиса.

Если токены зашифрованы, инспектор JWE покажет, что содержит JWE, созданный jose4j.

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

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/jose4j/run.sh запустила jose4j 0.9.7 в образе контейнера maven:3.9.16-eclipse-temurin-25; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Подпись и проверка работают для HS, RS, PS и ES на 256/384/512 и для EdDSA; ES256K, Ed25519 и Ed448 вызвали InvalidAlgorithmException.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA
    evidence jws-algorithms: ES256K: InvalidAlgorithmException: ES256K is an unknown, unsupported or unavailable alg algorithm (not one of [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]).
    evidence jws-algorithms: Ed25519: InvalidAlgorithmException: Ed25519 is an unknown, unsupported or unavailable alg algorithm (not one of [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]).
    evidence jws-algorithms: Ed448: InvalidAlgorithmException: Ed448 is an unknown, unsupported or unavailable alg algorithm (not one of [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]).
  • Кодировка подписи ES256

    raw

    Подписи ES256 — 64 байта в формате raw r‖s.

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

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой InvalidJwtSignatureException: JWT rejected due to invalid signature.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: InvalidJwtSignatureException: JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature:
  • Список разрешённых алгоритмов

    from-key

    Без ограничений на алгоритмы всё решает тип ключа: проверка токена RS256 ключом EC завершилась ошибкой The given key (algorithm=EC) is not valid for SHA256withRSA.

    Что записала проверка
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: JwtConsumer without algorithm constraints, RS256 token against an EC key: InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.InvalidKeyException: The given key (algorithm=EC) is not valid for SHA256withRSA):
  • Неподписанные токены (alg none)

    opt-in

    JwtConsumer с настройками по умолчанию отклоняет неподписанные токены с ошибкой 'none' is a blocked algorithm.; с setDisableRequireSignature() и без ограничений на алгоритмы он их принимает.

    Что записала проверка
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: default JwtConsumer: InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.InvalidAlgorithmException: 'none' is a blocked algorithm.):
    evidence jwt-alg-none: setDisableRequireSignature with no algorithm constraints: verified
  • Открытый ключ в роли секрета HMAC

    rejected

    Токен HS256, подписанный текстом PEM в качестве ключа, не прошёл проверку открытым ключом EC, но с HmacKey, созданным из байтов PEM, проверка прошла.

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: JwtConsumer with the EC public key: InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.InvalidKeyException: class sun.security.ec.ECPublicKeyImpl cannot be used for HMAC verification.):
    evidence jwt-hmac-public-key: JwtConsumer with an HmacKey built from the PEM bytes: verified
  • Сверка кривой с alg

    enforced

    При подписи ES256 ключом P-384 возникает ошибка ES256/SHA256withECDSA expects a key using P-256 but was P-384.

    Что записала проверка
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: InvalidKeyException: ES256/SHA256withECDSA expects a key using P-256 but was P-384
  • Допустимое расхождение часов по умолчанию

    0s

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

    Что записала проверка
    fact jwt-clock-skew = 0s
  • Обязательное утверждение о сроке действия

    no

    Токен без exp был принят; setRequireExpirationTime() делает это утверждение обязательным.

    Что записала проверка
    fact jwt-exp-required = no
  • Короткий секрет HMAC

    rejected

    HS256 отклонил 16-байтовый ключ: A key of the same size as the hash output ... or larger MUST be used.

    Что записала проверка
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: HS256 with a 16-byte key: InvalidKeyException: A key of the same size as the hash output (i.e. 256 bits for HS256) or larger MUST be used with the HMAC SHA algorithms but this key is only 128 bits

Использование

Подпишите токен ES256 сгенерированным JWK и обработайте его в JwtConsumer с фиксированным алгоритмом, обязательными издателем и exp и допуском на расхождение часов в 30 секунд.
import org.jose4j.jwa.AlgorithmConstraints.ConstraintType;
import org.jose4j.jwk.EcJwkGenerator;
import org.jose4j.jwk.EllipticCurveJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.EllipticCurves;

public class Usage {
    public static void main(String[] args) throws Exception {
        EllipticCurveJsonWebKey key = EcJwkGenerator.generateJwk(EllipticCurves.P256);
        JwtClaims claims = new JwtClaims();
        claims.setSubject("user-42");
        claims.setIssuer("https://auth.example.com");
        claims.setExpirationTimeMinutesInTheFuture(10);

        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
        jws.setKey(key.getPrivateKey());

        // Pin the algorithm and require exp; the defaults would allow any non-'none' alg for the key.
        JwtConsumer consumer = new JwtConsumerBuilder()
            .setJwsAlgorithmConstraints(ConstraintType.PERMIT, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256)
            .setVerificationKey(key.getPublicKey())
            .setExpectedIssuer("https://auth.example.com")
            .setRequireExpirationTime()
            .setAllowedClockSkewInSeconds(30)
            .build();
        System.out.println("sub: " + consumer.processToClaims(jws.getCompactSerialization()).getSubject());
    }
}

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера maven:3.9.16-eclipse-temurin-25, и запуск завершается ошибкой, если вывод не совпадает.

Подготовка
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Команда
java -cp 'lib/*' Usage.java
Вывод должен совпадать (регулярное выражение)
sub: user-42

Библиотеки