denoland/deno · error · Error

ERR_CRYPTO_INVALID_JWK

ERR_CRYPTO_INVALID_JWK

Error message

Invalid JWK

What it means

When importing an OKP JWK (options.format 'jwk') via createPublicKey/createPrivateKey, the base64-decoded x (public) or d (private) component must have the exact byte length of the curve: 32 for Ed25519/X25519, 57 for Ed448, 56 for X448. Any other length throws ERR_CRYPTO_INVALID_JWK ('Invalid JWK').

Source

Thrown at ext/node/polyfills/internal/crypto/keys.ts:277

    );
    validateString(key.x, "key.x");

    if (!isPublic) {
      validateString(key.d, "key.d");
    }

    let keyData;
    if (isPublic) {
      keyData = Buffer.from(key.x, "base64");
    } else {
      keyData = Buffer.from(key.d, "base64");
    }

    switch (key.crv) {
      case "Ed25519":
      case "X25519":
        if (TypedArrayPrototypeGetByteLength(keyData) !== 32) {
          throw new ERR_CRYPTO_INVALID_JWK();
        }
        break;
      case "Ed448":
        if (TypedArrayPrototypeGetByteLength(keyData) !== 57) {
          throw new ERR_CRYPTO_INVALID_JWK();
        }
        break;
      case "X448":
        if (TypedArrayPrototypeGetByteLength(keyData) !== 56) {
          throw new ERR_CRYPTO_INVALID_JWK();
        }
        break;
    }

    return op_node_create_ed_raw(key.crv, keyData, isPublic);
  }

  if (key.kty === "EC") {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Regenerate the JWK from a trusted source (WebCrypto subtle.exportKey('jwk', ...) or crypto key export) instead of hand-writing it
  2. Before importing, base64-decode x/d and check the byte length matches the curve (32/57/56)
  3. Make sure the crv field matches the key material actually embedded in the JWK

Example fix

// before
const jwk = { kty: 'OKP', crv: 'Ed448', x: x32Bytes, d: d32Bytes }; // wrong curve lengths
crypto.createPrivateKey({ key: jwk, format: 'jwk' });
// after
const jwk = { kty: 'OKP', crv: 'Ed448', x: x57Bytes, d: d57Bytes };
crypto.createPrivateKey({ key: jwk, format: 'jwk' });
Defensive patterns

Strategy: validation

Validate before calling

const OKP_LENGTHS = { Ed25519: 32, X25519: 32, Ed448: 57, X448: 56 };
function isValidOkpJwk(jwk) {
  if (jwk.kty !== 'OKP' || !(jwk.crv in OKP_LENGTHS)) return false;
  const material = jwk.d ?? jwk.x;
  return Buffer.from(material, 'base64').length === OKP_LENGTHS[jwk.crv];
}
if (!isValidOkpJwk(jwk)) throw new Error('JWK does not match its curve length');
crypto.createPrivateKey({ key: jwk, format: 'jwk' });

Type guard

function isOkpJwkWithValidLength(jwk) {
  const lens = { Ed25519: 32, X25519: 32, Ed448: 57, X448: 56 };
  return jwk?.kty === 'OKP' && jwk.crv in lens &&
    Buffer.from(jwk.d ?? jwk.x, 'base64').byteLength === lens[jwk.crv];
}

Try / catch

try {
  return crypto.createPrivateKey({ key: jwk, format: 'jwk' });
} catch (e) {
  if (e.code === 'ERR_CRYPTO_INVALID_JWK') throw new Error('JWK key material length does not match crv; re-export the key');
  throw e;
}

Prevention

When it happens

Trigger: createPrivateKey({ key: { kty: 'OKP', crv: 'Ed25519', x: '...', d: 'short-or-corrupt-base64' }, format: 'jwk' }) — truncated key material, a key generated for a different curve than the declared crv, or corrupted base64 that decodes to the wrong byte count.

Common situations: Hand-editing or truncating JWKs; transport through env vars/JSON that clips long values; generating an X448 key but declaring Ed448 (or vice versa); whitespace/padding damage that changes the decoded length.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/5400c9963d38c036. Report an issue: GitHub.