Error message
InvalidAccessError: Failed to execute 'exportKey' on 'SubtleCrypto': key is not extractable
Also printed as
- InvalidAccessError: The CryptoKey is nonextractable
- InvalidAccessError: A parameter or an operation is not supported by the underlying object
Chromium's text is above; Safari/WebKit: "The CryptoKey is nonextractable"; Firefox: "A parameter or an operation is not supported by the underlying object". All are `InvalidAccessError` DOMExceptions.
WebCrypto SubtleCrypto.exportKey / wrapKey — DOMException InvalidAccessError (Chromium / Firefox / WebKit)
What it means
The key was created with `extractable: false`, so its material can never be read back — not by `exportKey`, not by `wrapKey`. That is the point of the flag: the private key stays inside the browser's crypto implementation, and script (including injected script) can use it but not steal it. Public keys of a generated pair are always exportable; this error concerns private and secret keys.
Why it happens
The key was generated non-extractable on purpose
commonLibraries and passkey-like designs create device-bound keys this way. A later feature that wants to show, back up or sync the private key collides with that decision.
A key restored from IndexedDB keeps its flag
occasionalCryptoKeys stored in IndexedDB come back exactly as they were created; making them exportable later is impossible.
A library's default
occasionalSome JOSE and E2E libraries generate keys with `extractable: false` by default; their export helpers then fail for private keys.
How to fix it
- 1.
Keep the private key as an object, export the public key
Store non-extractable keys as `CryptoKey` objects in IndexedDB and share only the public key (SPKI or JWK). This keeps the protection the flag provides.
js const pair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, false, ["sign", "verify"]); // Persist the non-extractable private key as a CryptoKey object (structured clone), never as bytes: await idbPut("signing-key", pair.privateKey); // Share only the public key: const jwk = await crypto.subtle.exportKey("jwk", pair.publicKey); - 2.
Create extractable keys only when export is a requirement
If the private key genuinely must leave the browser, generate it extractable and export it only wrapped with another key.
js // Only if the private key must leave the browser (e.g. a user-initiated, encrypted backup): const pair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]); const wrapped = await crypto.subtle.wrapKey("pkcs8", pair.privateKey, backupKey, { name: "AES-GCM", iv });