denoland/deno · error · DOMException

Unsupported key usage for a PBKDF2 key

Error message

Unsupported key usage for a PBKDF2 key

What it means

Thrown by SecretKeyObject.toCryptoKey() when the algorithm is 'PBKDF2' and the usages array is non-empty and contains any entry other than 'deriveKey' or 'deriveBits'. WebCrypto restricts PBKDF2 keys to key-derivation inputs only, so entries like 'encrypt' or 'sign' are rejected with a SyntaxError DOMException. Note the guard only fires for non-empty arrays — an empty usages array is accepted for PBKDF2 in this polyfill.

Source

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

      );
    }

    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") {
      if (extractable) {
        throw new DOMException(
          "HKDF keys are not extractable",
          "SyntaxError",
        );
      }
      if (
        usages.length > 0 &&
        ArrayPrototypeSome(
          usages,
          (u: string) =>
            !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
        )

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Use only 'deriveKey' and/or 'deriveBits' in the usages array
  2. Pass an empty usages array [] — the PBKDF2 branch accepts it
  3. Do encryption/signing with the key you derive via crypto.subtle.deriveBits/deriveKey, never with the PBKDF2 key itself

Example fix

// before
const key = createSecretKey(pw).toCryptoKey('PBKDF2', false, ['sign', 'verify']); // throws

// after
const key = createSecretKey(pw).toCryptoKey('PBKDF2', false, ['deriveBits']);
const aesKey = await crypto.subtle.deriveKey({ name: 'PBKDF2' }, key, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = ['deriveKey', 'deriveBits'];
const usages = requestedUsages.filter((u) => ALLOWED.includes(u));
const key = secretKeyObject.toCryptoKey('PBKDF2', false, usages);

Type guard

const isDerivationUsage = (u: string): boolean =>
  u === 'deriveKey' || u === 'deriveBits';

Try / catch

try {
  key = secretKeyObject.toCryptoKey('PBKDF2', false, usages);
} catch (e) {
  if (e instanceof DOMException && e.name === 'SyntaxError' && /Unsupported key usage/.test(e.message)) {
    key = secretKeyObject.toCryptoKey('PBKDF2', false, ['deriveBits']);
  } else throw e;
}

Prevention

When it happens

Trigger: createSecretKey(pw).toCryptoKey('PBKDF2', false, ['encrypt', 'deriveBits']) — any usage outside ['deriveKey','deriveBits'] while at least one usage is present.

Common situations: Reusing an HMAC usages array (['sign','verify']) for a PBKDF2 import; generic wrappers that pass the union of all possible usages; migrating code between subtle.importKey and KeyObject conversion where the same rule applies.

Related errors


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