本文へスキップ
ecdsa.com
Java · Kotlin

JJWT (io.jsonwebtoken)

Les Hazlewood による JJWT は、Java と Android で広く使われている JWT ライブラリです。検証鍵によって受け入れるアルゴリズムが決まり、署名のないトークンには明示的な unsecured() パーサーが必要で、弱い HMAC 鍵は無条件に拒否されます。
ライセンス
Apache-2.0
確認したバージョン
0.13.0 ·
ソースコード
github.com/jwtk/jjwt
ドキュメント
github.com/jwtk/jjwt#readme

互換性の概要

JOSE と JWT

JWS アルゴリズム
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

JJWT 0.12 以降は、メソッドチェーンで書くビルダーとパーサー(Jwts.builder()…signWith(key, alg) と Jwts.parser().verifyWith(key)…parseSignedClaims(token))を提供し、アルゴリズムは Jwts.SIG にまとめられています。配布は 3 つのアーティファクトに分かれており、コンパイル時には jjwt-api、実行時には jjwt-impl と、jjwt-jackson などの JSON バインディングを使います。0.13.0 は 256/384/512 の HS、RS、PS、ES と EdDSA で署名と検証ができました。ES256K と、Ed25519、Ed448 という名前は Jwts.SIG にありません。

パーサーは検証鍵から許可するアルゴリズムを導き、エラーメッセージもそれをはっきり示します。EC 公開鍵に対する RS256 トークンと、EC 鍵の PEM テキストを鍵とした HS256 トークンは、どちらも、その鍵はそのアルゴリズムには使えないと説明する UnsupportedJwtException で失敗しました。保護しているのはアルゴリズムの一覧ではなく鍵の型です。Keys.hmacShaKeyFor(pemBytes) を渡したパーサーはその偽造トークンの検証を通したため、公開されうるデータから HMAC 鍵を作ってはいけません。

署名のないトークンは、parseSignedClaims が RFC 7518 §3.6 を引用するメッセージで拒否し、読み取れるのは Jwts.parser().unsecured()…parseUnsecuredClaims を通した場合だけです。弱い HMAC 鍵はそもそもそこまで到達しません。RFC 7518 はハッシュ以上の長さの鍵を求めているため、Keys.hmacShaKeyFor は 16 バイトの鍵を WeakKeyException で拒否しました。ES256 は署名時に P-384 の鍵を拒否します。

注意が必要なデフォルトが 2 つあります。時刻のずれは許容されず(3 秒前に期限切れになったトークンは失敗しました)、exp も任意です。サンプルのように clockSkewSeconds を設定し、有効期限と発行者をチェックしてください。ES256 の署名は 64 バイトの生の r‖s で、DER の署名は正確に報告されます(Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes))。JJWT は、エンコーディングの問題を名指しする数少ないライブラリの 1 つです。トークンは JWT デバッガーで、HMAC 鍵は JWT シークレットチェッカーで確認してください。

JWE や JWK セットを大規模に扱う場合は、Nimbus JOSE + JWT と比較してください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/jjwt/run.sh がコンテナイメージ maven:3.9.16-eclipse-temurin-25 内で JJWT (io.jsonwebtoken) 0.13.0 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • JWS アルゴリズム

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    256/384/512 の HS、RS、PS、ES と EdDSA で署名と検証ができました。ES256K、Ed25519、Ed448 は Jwts.SIG にありません。

    チェックによる記録
    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
  • ES256 署名のエンコーディング

    raw

    ES256 の署名は 64 バイトの生の r‖s です。

    チェックによる記録
    fact jws-ecdsa-encoding = raw
  • JWS 内の DER 署名

    rejected

    DER エンコードの ES256 署名は Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes) で失敗します。

    チェックによる記録
    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).
  • アルゴリズムの許可リスト

    from-key

    アルゴリズムは検証鍵で決まります。EC 公開鍵に対して RS256 トークンを検証すると、UnsupportedJwtException で失敗しました。

    チェックによる記録
    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.
  • 署名なしトークン(alg none)

    opt-in

    parseSignedClaims は署名のないトークンを拒否します。読み取るには Jwts.parser().unsecured() と parseUnsecuredClaims を使います。

    チェックによる記録
    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
  • HMAC シークレットとして使われる公開鍵

    rejected

    PEM テキストを鍵とした HS256 トークンは、EC 公開鍵に対しては拒否されましたが、Keys.hmacShaKeyFor(pemBytes) では検証を通りました。

    チェックによる記録
    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
  • alg と曲線の照合

    enforced

    P-384 の鍵で ES256 の署名を行うと失敗します(the 'ES256' algorithm requires EC Keys with 256 bits)。

    チェックによる記録
    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).
  • デフォルトのクロックスキュー

    0s

    デフォルトでは時刻のずれを許容しません。3 秒前に期限切れになったトークンは拒否されました。

    チェックによる記録
    fact jwt-clock-skew = 0s
  • 有効期限クレームの要否

    no

    exp のないトークンも正常に解析されました。

    チェックによる記録
    fact jwt-exp-required = no
  • 短い HMAC シークレット

    rejected

    Keys.hmacShaKeyFor は 16 バイトの鍵を WeakKeyException で拒否しました。

    チェックによる記録
    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.

使い方

ES256 トークンを発行し、鍵を指定し、発行者を必須にして、30 秒の時刻のずれを許容して解析します。
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));
    }
}

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ 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, has exp: true

ライブラリ