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

JWE Header epk value is not an Elliptic Curve Public JWK. Va

Error message

JWE Header epk value is not an Elliptic Curve Public JWK. Value: ${epk}

What it means

This error means the `epk` (ephemeral public key) JWK found in the JWE protected header is not an Elliptic Curve Public JWK of the type the recipient key's curve requires (an EcPublicJwk for standard EC curves, or an OctetPublicJwk for OkP/Edwards curves). During ECDH key agreement decryption, getDecryptionKey validates that the sender's ephemeral key material is a proper EC public JWK before deriving the shared key; if it is absent, malformed, or the wrong JWK family, an InvalidKeyException is thrown.

Source

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

        return result;
    }

    @Override
    public SecretKey getDecryptionKey(DecryptionKeyRequest<PrivateKey> request) throws SecurityException {

        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. Verify the sender encrypts with the matching ECDH-ES algorithm and curve for your recipient key (e.g. ECDH-ES+A256KW with an EC key on the same curve).
  2. Inspect the JWE protected header and confirm the `epk` field is a complete EC/OKP public JWK with kty and crv.
  3. Regenerate the token with a maintained jjwt (or other spec-compliant) producer rather than hand-assembling headers.
  4. Confirm the recipient PrivateKey curve family matches the epk curve family (EcPublicJwk vs OctetPublicJwk).

Example fix

// before: encrypting with a mismatched recipient key type
Keysbuilder kb = new KeysBuilder(spec); // recipient key is an EC key but token built for OKP
// after
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // or use the EC key with ECDH-ES
String jwe = Jwts.builder().setHeader(...).encryptWith(key, Jwts.KEY.Alg.ECDH_ES_A256KW, A256GCM);
Defensive patterns

Strategy: validation

Validate before calling

Object epk = header.get("epk");
boolean ok = epk instanceof Map && ((Map<?,?>) epk).get("kty") instanceof String
    && (((String)((Map<?,?>)epk).get("kty")).equals("EC") || ((String)((Map<?,?>)epk).get("kty")).equals("OKP"));
if (!ok) throw new IllegalArgumentException("JWE header missing a valid EC/OKP epk JWK");

Type guard

boolean isEcPublicJwk(Object o) {
  return o instanceof Map && "EC".equals(((Map<?,?>)o).get("kty")) && ((Map<?,?>)o).containsKey("crv");
}

Try / catch

try {
  Jwe<Claims> jwe = Jwts.parser().decryptWith(ecPrivateKey).build().parseEncryptedClaims(token);
} catch (InvalidKeyException e) {
  // epk header invalid or off-curve: reject token / request re-encryption
}

Prevention

When it happens

Trigger: Decrypting a JWE that was produced with an ECDH-ES key-management algorithm whose protected header contains an `epk` value that is missing, not a JSON object, not a JWK with a proper `kty`/`crv`, or whose curve family mismatches the recipient key (e.g. an octet key pair epk when decrypting with an EC P-256 private key).

Common situations: Hand-rolled token producers that omit or mangle the epk header; a sender and recipient using different curve families (EC vs OKP); tokens tampered with or truncated; using a recipient key type that does not match how the token was encrypted.

Related errors


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