jwtk/jjwt · error · io.jsonwebtoken.security.InvalidKeyException

JWE Header epk value does not represent a point on the expec

Error message

JWE Header epk value does not represent a point on the expected curve. Value: ${epk}

What it means

During ECDH-ES decryption, after confirming the header's `epk` is the right JWK type, jjwt checks that the ephemeral public key's point actually lies on the recipient key's curve (curve.contains(epk.toKey())). If the point is off-curve, the key material is invalid and decryption is aborted with an InvalidKeyException, since continuing would allow invalid-curve attacks.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java:226

        Assert.notNull(request, "Request cannot be null.");
        JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
        PrivateKey privateKey = Assert.notNull(request.getKey(), "Decryption PrivateKey cannot be null.");
        ParameterReadable reader = new RequiredParameterReader(header);
        PublicJwk<?> epk = reader.get(DefaultJweHeader.EPK);

        AbstractCurve curve = assertCurve(privateKey);
        Assert.stateNotNull(curve, "Internal implementation state: Curve cannot be null.");
        Class<?> epkClass = curve instanceof ECCurve ? EcPublicJwk.class : OctetPublicJwk.class;
        if (!epkClass.isInstance(epk)) {
            String msg = "JWE Header " + DefaultJweHeader.EPK + " value is not an Elliptic Curve " +
                    "Public JWK. Value: " + epk;
            throw new InvalidKeyException(msg);
        }
        if (!curve.contains(epk.toKey())) {
            String msg = "JWE Header " + DefaultJweHeader.EPK + " value does not represent " +
                    "a point on the expected curve. Value: " + epk;
            throw new InvalidKeyException(msg);
        }

        final SecretKey derived = deriveKey(request, epk.toKey(), privateKey);

        DecryptionKeyRequest<SecretKey> unwrapReq = new DefaultDecryptionKeyRequest<>(request.getPayload(),
                null, request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derived);

        return WRAP_ALG.getDecryptionKey(unwrapReq);
    }
}

View on GitHub (pinned to fb71496164)

Solutions

  1. Ensure both parties use the same named curve (crv) for the recipient key and epk.
  2. Re-encrypt the token with a compliant library; do not manually edit header values.
  3. If attacks are a concern, this error is jjwt correctly rejecting an invalid-curve input — audit the token source.
  4. Verify no base64url corruption of the compact token (wrong padding/characters) when transporting it.

Example fix

// before: recipient key on P-384 while sender encrypts with P-256
ECPrivateKey key = (ECPrivateKey) Keys.privateKeyFor(SignatureAlgorithm.ES384);
// after: use matching curves on both sides
ECPrivateKey key = (ECPrivateKey) Keys.privateKeyFor(SignatureAlgorithm.ES256);
Defensive patterns

Strategy: validation

Validate before calling

String crv = (String) ((Map<?,?>) header.get("epk")).get("crv");
if (!expectedCurveId.equals(crv)) throw new IllegalArgumentException("epk curve mismatch: " + crv);

Type guard

boolean isPointOnExpectedCurve(Object epk, String expectedCrv) {
  return epk instanceof Map && expectedCrv.equals(((Map<?,?>)epk).get("crv"));
}

Try / catch

try {
  Jwe<Claims> jwe = Jwts.parser().decryptWith(privateKey).build().parseEncryptedClaims(token);
} catch (InvalidKeyException e) {
  // treat as invalid/tampered token: reject and log the curve mismatch
}

Prevention

When it happens

Trigger: Decrypting a JWE whose `epk` header decodes to a point not on the recipient's named curve — e.g. coordinates crafted or corrupted, an epk generated for a different curve (P-256 epk with P-384 recipient key), or truncated/byte-mangled x/y or raw x coordinates.

Common situations: Sender and recipient disagree on the curve (crv mismatch); tokens produced by non-compliant libraries that do not validate curve membership; deliberate invalid-curve attack attempts; corrupted tokens in transit.

Related errors


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/1ad489e827abd87f. Report an issue: GitHub.