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

Invalid ${id} encoded ${PublicKey|PrivateKey} length. Should

Error message

Invalid ${id} encoded ${PublicKey|PrivateKey} length. Should be ${expected}, found ${actual}.

What it means

Raw Edwards curve keys have a fixed encoded length per curve (e.g. 32 bytes for Ed25519/X25519, 57 for Ed448/X448). assertLength is called from toPublicKey/toPrivateKey and rejects raw x or d byte arrays whose length does not match the curve's expected encodedKeyByteLength, throwing an InvalidKeyException.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsCurve.java:253

                }
            }
            Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length.");
            byte[] result = Arrays.copyOfRange(encoded, i, i + keyLen);
            keyLen = Bytes.length(result);
            Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length.");
            return result;
        } finally {
            Bytes.clear(encoded);
        }
    }

    private void assertLength(byte[] raw, boolean isPublic) {
        int len = Bytes.length(raw);
        if (len != this.encodedKeyByteLength) {
            String msg = "Invalid " + getId() + " encoded " + (isPublic ? "PublicKey" : "PrivateKey") +
                    " length. Should be " + Bytes.bytesMsg(this.encodedKeyByteLength) + ", found " +
                    Bytes.bytesMsg(len) + ".";
            throw new InvalidKeyException(msg);
        }
    }

    public PublicKey toPublicKey(byte[] x, Provider provider) {
        assertLength(x, true);
        final byte[] encoded = Bytes.concat(this.PUBLIC_KEY_ASN1_PREFIX, x);
        final X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
        JcaTemplate template = new JcaTemplate(getJcaName(), provider);
        return template.generatePublic(spec);
    }

    KeySpec privateKeySpec(byte[] d, boolean standard) {
        byte[] prefix = standard ? this.PRIVATE_KEY_ASN1_PREFIX : this.PRIVATE_KEY_JDK11_PREFIX;
        byte[] encoded = Bytes.concat(prefix, d);
        return new PKCS8EncodedKeySpec(encoded);
    }

    public PrivateKey toPrivateKey(final byte[] d, Provider provider) {

View on GitHub (pinned to fb71496164)

Solutions

  1. Check the byte array length and trim/pad to the curve's canonical size (32 bytes for 255519-family, 57 for 448-family).
  2. Decode the JWK value with a proper base64url decoder without padding (e.g. io.jsonwebtoken.lang.Base64Url or java.util.Base64.getUrlDecoder).
  3. Confirm you are using the curve instance matching the key's crv (EdwardsCurve.Ed25519 for 32-byte keys, not Ed448).
  4. If you have a 64-byte seed+pub concatenation, use only the first 32 bytes for the private key.

Example fix

// before
byte[] d = Base64.getDecoder().decode(jwk.get("d")); // wrong decoder, wrong length
PrivateKey pk = EdwardsCurve.Ed25519.toPrivateKey(d, null);
// after
byte[] d = Base64.getUrlDecoder().decode((String) jwk.get("d"));
if (d.length != 32) throw new IllegalArgumentException("Ed25519 private key must be 32 bytes");
PrivateKey pk = EdwardsCurve.Ed25519.toPrivateKey(d, null);
Defensive patterns

Strategy: validation

Validate before calling

if (d.length != 32) { // Ed25519/X25519
  throw new IllegalArgumentException("Expected 32-byte raw key, got " + d.length);
}

Type guard

boolean hasCanonicalLength(byte[] raw, int expected) {
  return raw != null && raw.length == expected; // 32 for 25519-family, 57 for 448-family
}

Try / catch

try {
  PrivateKey pk = EdwardsCurve.Ed25519.toPrivateKey(d, null);
} catch (InvalidKeyException e) {
  // length mismatch: fix decoding / curve selection
}

Prevention

When it happens

Trigger: Calling EdwardsCurve.toPublicKey(byte[] x)/toPrivateKey(byte[] d) (directly or via Jwk parsing of an OKP JWK's x/d) with a byte array that is shorter or longer than the curve's canonical size — e.g. 64-byte signature instead of a 32-byte key, hex/base64 decoded incorrectly, or an Ed25519 key parsed as Ed448.

Common situations: Decoding the JWK x value with the wrong base64 variant (standard vs base64url) or with whitespace; concatenating seed+public key (64 bytes) and passing it as the private key; assuming all curves use 32-byte keys.

Related errors


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