Pular para o conteúdo
ecdsa.com
Java · Kotlin

JJWT (io.jsonwebtoken)

O JJWT, de Les Hazlewood, uma biblioteca JWT muito usada em Java e Android: a chave de verificação define o algoritmo aceito, tokens sem assinatura exigem um parser unsecured() explícito, e chaves HMAC fracas são recusadas de imediato.
Licença
Apache-2.0
Versão verificada
0.13.0 ·
Código-fonte
github.com/jwtk/jjwt
Documentação
github.com/jwtk/jjwt#readme

Visão geral da compatibilidade

JOSE e JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

O JJWT 0.12 e versões posteriores expõem um builder e um parser fluentes — Jwts.builder()…signWith(key, alg) e Jwts.parser().verifyWith(key)…parseSignedClaims(token) —, com os algoritmos em Jwts.SIG. Ele é distribuído em três artefatos: jjwt-api em tempo de compilação e, em tempo de execução, jjwt-impl e um módulo de serialização JSON, como o jjwt-jackson. A versão 0.13.0 fez o ciclo completo de ida e volta com HS, RS, PS e ES em 256/384/512 e com EdDSA; ES256K e os nomes Ed25519 e Ed448 não estão em Jwts.SIG.

O parser deriva o algoritmo permitido da chave de verificação, e seus erros dizem isso com clareza. Um token RS256 contra uma chave pública EC e um token HS256 que usava como chave o texto PEM da chave EC falharam com UnsupportedJwtException, que explica que a chave não pode ser usada com aquele algoritmo. A proteção é o tipo da chave, não uma lista: um parser que recebeu Keys.hmacShaKeyFor(pemBytes) verificou esse token forjado; por isso, nunca crie chaves HMAC a partir de material que possa ser público.

Tokens sem assinatura são recusados por parseSignedClaims com uma mensagem que cita a RFC 7518 §3.6, e só podem ser lidos por meio de Jwts.parser().unsecured()…parseUnsecuredClaims. Chaves HMAC fracas nem chegam até aí: Keys.hmacShaKeyFor rejeitou uma chave de 16 bytes com WeakKeyException, já que a RFC 7518 exige chaves pelo menos do tamanho do hash. O ES256 recusa uma chave P-384 no momento da assinatura.

Dois padrões exigem atenção. Não há tolerância para diferença de relógio — um token expirado três segundos antes falhou — e exp é opcional; por isso, defina clockSkewSeconds e verifique a expiração e o emissor, como faz o exemplo. As assinaturas ES256 são o r‖s bruto de 64 bytes; uma assinatura DER é relatada com precisão (Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes)), e o JJWT é uma das poucas bibliotecas que apontam o problema de codificação. Inspecione tokens no depurador de JWT e chaves HMAC com o verificador de segredos JWT.

Para lidar com JWE e conjuntos de chaves JWK em escala, compare com o Nimbus JOSE + JWT.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/jjwt/run.sh executou JJWT (io.jsonwebtoken) 0.13.0 na imagem de contêiner maven:3.9.16-eclipse-temurin-25 em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Assinatura e verificação funcionaram com HS, RS, PS e ES em 256/384/512 e com EdDSA; ES256K, Ed25519 e Ed448 não estão em Jwts.SIG.

    Registrado pela verificação
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA
    evidence jws-algorithms: ES256K: not in Jwts.SIG
    evidence jws-algorithms: Ed25519: not in Jwts.SIG
    evidence jws-algorithms: Ed448: not in Jwts.SIG
  • Codificação da assinatura ES256

    raw

    As assinaturas ES256 são 64 bytes de r‖s brutos.

    Registrado pela verificação
    fact jws-ecdsa-encoding = raw
  • Assinatura DER dentro de um JWS

    rejected

    Uma assinatura ES256 codificada em DER falha com Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes).

    Registrado pela verificação
    fact jws-der-signature = rejected
    evidence jws-der-signature: SignatureException: Unable to verify Elliptic Curve signature using provided ECPublicKey: Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes) per [RFC 7518, Section 3.4 (validation)](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).
  • Lista de algoritmos permitidos

    from-key

    A chave de verificação determina o algoritmo: um token RS256 verificado com uma chave pública EC falhou com UnsupportedJwtException.

    Registrado pela verificação
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: RS256 token, verifyWith(EC public key): UnsupportedJwtException: The parsed JWT indicates it was signed with the 'RS256' signature algorithm, but the provided sun.security.ec.ECPublicKeyImpl key may not be used to verify RS256 signatures.  Because the specified key reflects a specific and expected algorithm, and the JWT does not reflect this algorithm, it is likely that the JWT was not expected and therefore should not be trusted.  Another possibility is that the parser was provided the incorrect signature verification key, but this cannot be assumed for security reasons.
  • Tokens sem assinatura (alg none)

    opt-in

    parseSignedClaims rejeita tokens sem assinatura; Jwts.parser().unsecured() com parseUnsecuredClaims os lê.

    Registrado pela verificação
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: parseSignedClaims with the EC key: UnsupportedJwtException: Unsecured JWSs (those with an 'alg' (Algorithm) header value of 'none') are disallowed by default as mandated by https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6. If you wish to allow them to be parsed, call the JwtParserBuilder.unsecured() method, but please read the security considerations covered in that method's JavaDoc before doing so. Header: {alg=none, typ=JWT}
    evidence jwt-alg-none: parser().unsecured().parseUnsecuredClaims: verified
  • Chave pública usada como segredo HMAC

    rejected

    Um token HS256 que usava o texto PEM como chave foi rejeitado com a chave pública EC, mas foi verificado com Keys.hmacShaKeyFor(pemBytes).

    Registrado pela verificação
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: verifyWith(EC public key): UnsupportedJwtException: The parsed JWT indicates it was signed with the 'HS256' signature algorithm, but the provided sun.security.ec.ECPublicKeyImpl key may not be used to verify HS256 signatures.  Because the specified key reflects a specific and expected algorithm, and the JWT does not reflect this algorithm, it is likely that the JWT was not expected and therefore should not be trusted.  Another possibility is that the parser was provided the incorrect signature verification key, but this cannot be assumed for security reasons.
    evidence jwt-hmac-public-key: verifyWith(Keys.hmacShaKeyFor(PEM bytes)): verified
  • Curva conferida com o alg

    enforced

    Assinar ES256 com uma chave P-384 falha: the 'ES256' algorithm requires EC Keys with 256 bits.

    Registrado pela verificação
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: SignatureException: Unable to compute ES256 signature. Cause: The provided Elliptic Curve signing key size (aka order bit length) is 384 bits (48 bytes), but the 'ES256' algorithm requires EC Keys with 256 bits (32 bytes) per [RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).
  • Tolerância padrão de diferença de relógio

    0s

    Sem tolerância para diferença de relógio por padrão: um token expirado três segundos antes foi rejeitado.

    Registrado pela verificação
    fact jwt-clock-skew = 0s
  • Claim de expiração obrigatória

    no

    Um token sem exp foi analisado com sucesso.

    Registrado pela verificação
    fact jwt-exp-required = no
  • Segredo HMAC curto

    rejected

    Keys.hmacShaKeyFor rejeitou uma chave de 16 bytes com WeakKeyException.

    Registrado pela verificação
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: Keys.hmacShaKeyFor(16 bytes): WeakKeyException: The specified key byte array is 128 bits which is not secure enough for any JWT HMAC-SHA algorithm.  The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size).  Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm.  See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.

Uso

Emita um token ES256 e analise-o com a chave, o emissor obrigatório e 30 segundos de tolerância para diferença de relógio.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.security.KeyPair;
import java.util.Date;

public class Usage {
    public static void main(String[] args) {
        KeyPair pair = Jwts.SIG.ES256.keyPair().build();
        String token = Jwts.builder()
            .subject("user-42")
            .issuer("https://auth.example.com")
            .expiration(new Date(System.currentTimeMillis() + 600_000))
            .signWith(pair.getPrivate(), Jwts.SIG.ES256)
            .compact();

        // The key decides which algorithms are acceptable; require iss and exp explicitly
        // and allow some clock skew (the default is none).
        Claims claims = Jwts.parser()
            .verifyWith(pair.getPublic())
            .requireIssuer("https://auth.example.com")
            .clockSkewSeconds(30)
            .build()
            .parseSignedClaims(token)
            .getPayload();
        System.out.println("sub: " + claims.getSubject() + ", has exp: " + (claims.getExpiration() != null));
    }
}

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner maven:3.9.16-eclipse-temurin-25, e a execução falha se a saída não corresponder.

Configuração
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Comando
java -cp 'lib/*' Usage.java
A saída deve corresponder a (expressão regular)
sub: user-42, has exp: true

Bibliotecas