lcobucci/jwt · error · Lcobucci\JWT\Signer\InvalidKeyProvided

The length of the provided key is different than

Error message

The length of the provided key is different than {expectedLength} bits, {actualLength} bits provided

What it means

After confirming the key is an EC key, Ecdsa's guardAgainstIncompatibleKey compares the key's bit length (details['bits']) with the length required by the concrete signer via expectedKeyLength() (256 for ES256, 384 for ES384, 512 for ES512). A mismatch throws InvalidKeyProvided::incompatibleKeyLength, because an ECDSA signature scheme is bound to a specific curve/key size.

Solutions

  1. Match the curve to the algorithm: prime256v1 for ES256 (256 bits), secp384r1 for ES384 (384 bits), secp521r1 for ES512 (521 bits — note the signer's expected length semantics).
  2. Check the actual key size: openssl_pkey_get_details(openssl_pkey_get_private($pem))['bits'] and compare with the signer's expectedKeyLength().
  3. Regenerate the key pair with the correct curve: openssl ecparam -name secp384r1 -genkey -noout -out key.pem.
  4. Or instantiate the signer matching the key you already have (Es256 for a 256-bit key, etc.) instead of changing keys.

Example fix

// before
$signer = new Es384();
$key = new Key(file_get_contents('p256-key.pem')); // 256 bits -> incompatibleKeyLength

// after
shell_exec('openssl ecparam -name secp384r1 -genkey -noout -out p384-key.pem');
$signer = new Es384();
$key = new Key(file_get_contents('p384-key.pem')); // 384 bits
Defensive patterns

Strategy: validation

Validate before calling

$details = openssl_pkey_get_details(openssl_pkey_get_private($pem));
if (($details['bits'] ?? 0) !== $signer::expectedBits()) { // e.g. 256 for Es256
    throw new InvalidArgumentException(sprintf('Key is %d bits; signer requires %d bits', $details['bits'] ?? 0, $signer::expectedBits()));
}

Type guard

function keyMatchesEcdsaBits(string $pem, int $expectedBits): bool
{
    $key = openssl_pkey_get_private($pem);
    $details = $key === false ? null : openssl_pkey_get_details($key);
    return ($details['bits'] ?? -1) === $expectedBits;
}

Try / catch

try {
    $signature = $signer->sign($payload, $key);
} catch (InvalidKeyProvided $e) {
    throw new ConfigurationException('ECDSA key length mismatch: ' . $e->getMessage(), previous: $e);
}

Prevention

When it happens

Trigger: Using e.g. an ES384 signer with a P-256 (256-bit) key, or an ES256 signer with a P-521 key — sign() or verify() resolves the key details and calls guardAgainstIncompatibleKey($type, $bits).

Common situations: Generating an EC key without matching the curve to the algorithm (openssl ecparam default curve differs from the signer's expectation); switching the JWT algorithm in config from ES256 to ES384 without regenerating keys; hardcoding one key pair for multiple signers.

Related errors


AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14). Data as JSON: /api/errors/304d176ec88e9f32. Report an issue: GitHub.

Appendix: source

Thrown at src/Signer/Ecdsa.php:48

            $payload,
            $key,
        );
    }

    /** {@inheritDoc} */
    final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void
    {
        if ($type !== OPENSSL_KEYTYPE_EC) {
            throw InvalidKeyProvided::incompatibleKeyType(
                self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_EC],
                self::KEY_TYPE_MAP[$type] ?? 'unknown',
            );
        }

        $expectedKeyLength = $this->expectedKeyLength();

        if ($lengthInBits !== $expectedKeyLength) {
            throw InvalidKeyProvided::incompatibleKeyLength($expectedKeyLength, $lengthInBits);
        }
    }

    /** {@inheritDoc} */
    final protected function guardAgainstIncompatibleCurve(?string $curveName): void
    {
        $expectedCurve = $this->expectedCurve();

        if ($curveName !== $expectedCurve) {
            throw InvalidKeyProvided::incompatibleKeyCurve($expectedCurve, $curveName ?? 'unknown');
        }
    }

    /**
     * @internal
     *
     * @return positive-int
     */

View on GitHub (pinned to 375813049c)