denoland/deno · error · DOMException

PBKDF2 keys are not extractable

Error message

PBKDF2 keys are not extractable

What it means

Thrown by SecretKeyObject.toCryptoKey() in Deno's node:crypto polyfill when a secret KeyObject is converted to a WebCrypto CryptoKey with algorithm name 'PBKDF2' and extractable=true. The WebCrypto specification requires PBKDF2 password material to be non-extractable, because the raw password must never be read back out of the CryptoKey. Deno enforces this with a DOMException of type SyntaxError, matching Node.js and browsers. It is a usage error, not a problem with the key itself.

Source

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

    extractable: boolean,
    usages: string[],
  ): CryptoKey {
    const algName = typeof algorithm === "string"
      ? algorithm
      : (algorithm as { name: string }).name;

    const rawData = new Uint8Array(op_node_export_secret_key(this[kHandle]));

    if (TypedArrayPrototypeGetByteLength(rawData) === 0) {
      throw new DOMException(
        "Zero-length key is not supported",
        "DataError",
      );
    }

    if (algName === "PBKDF2") {
      if (extractable) {
        throw new DOMException(
          "PBKDF2 keys are not extractable",
          "SyntaxError",
        );
      }
      if (
        usages.length > 0 &&
        ArrayPrototypeSome(
          usages,
          (u: string) =>
            !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
        )
      ) {
        throw new DOMException(
          "Unsupported key usage for a PBKDF2 key",
          "SyntaxError",
        );
      }
    } else if (algName === "HKDF") {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass extractable=false when the algorithm is PBKDF2 (or HKDF)
  2. If you need the raw password bytes, keep them in your own buffer or call keyObject.export() on the KeyObject instead of extracting a CryptoKey
  3. In generic wrappers, force extractable=false for derivation algorithms before calling toCryptoKey/importKey

Example fix

// before
const key = createSecretKey(pw).toCryptoKey('PBKDF2', true, ['deriveBits']); // DOMException SyntaxError

// after
const key = createSecretKey(pw).toCryptoKey('PBKDF2', false, ['deriveBits']);
Defensive patterns

Strategy: validation

Validate before calling

const isDerivationAlg = (name) => name === 'PBKDF2' || name === 'HKDF';
const algName = typeof algorithm === 'string' ? algorithm : algorithm.name;
const extractable = isDerivationAlg(algName) ? false : requestedExtractable;
const key = secretKeyObject.toCryptoKey(algorithm, extractable, usages);

Type guard

function isNonExtractableOnlyAlgorithm(name: string): boolean {
  return name === 'PBKDF2' || name === 'HKDF';
}

Try / catch

try {
  const key = secretKeyObject.toCryptoKey(algorithm, extractable, usages);
} catch (e) {
  if (e instanceof DOMException && e.name === 'SyntaxError' && e.message.includes('not extractable')) {
    // retry once with extractable=false, or surface a config error
  } else throw e;
}

Prevention

When it happens

Trigger: Calling createSecretKey(password).toCryptoKey('PBKDF2', true, ['deriveBits']) — or any KeyObject-to-CryptoKey interop path that forwards extractable=true while the algorithm name is 'PBKDF2'.

Common situations: Copy-pasting a subtle.importKey('raw', ..., true, ...) example written for HMAC/AES into PBKDF2 code; generic key wrappers that always request extractable keys; porting browser snippets where the extractable flag comes from a variable the user controls.

Related errors


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