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
- Regenerate the JWK from a trusted source (WebCrypto subtle.exportKey('jwk', ...) or crypto key export) instead of hand-writing it
- Before importing, base64-decode x/d and check the byte length matches the curve (32/57/56)
- 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
- Produce JWKs only via standard exports, never by hand
- Validate x/d byte lengths against the curve before importing
- Beware transports that trim or corrupt long base64 strings
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
- ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED
- ERR_INVALID_ARG_TYPE
- ERR_INVALID_ARG_TYPE
- ERR_CRYPTO_UNKNOWN_CIPHER
- ERR_CRYPTO_INVALID_STATE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/5400c9963d38c036.
Report an issue: GitHub.