Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions doc/api/webcrypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,73 @@ async function digest(data, algorithm = 'SHA-512') {
}
```

### Checking for runtime algorithm support

> Stability: 1.0 - Early development. SubleCrypto.supports is an experimental
> implementation based on [Modern Algorithms in the Web Cryptography API][]

This example derives a key from a password using Argon2, if available,
Copy link
Member

@jasnell jasnell Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit more introductory text here at the start would be good, otherwise at first glance someone might thing this section is only about Argon2. Something like, The `SubtleCrypto.supports(...)` API provides a way of detecting if a particular algorithm or set of options are supported.

or PBKDF2, otherwise; and then encrypts and decrypts some text with it
using AES-OCB, if available, and AES-GCM, otherwise.

```mjs
const password = 'correct horse battery staple';
const derivationAlg =
SubtleCrypto.supports?.('importKey', 'Argon2id') ?
'Argon2id' :
'PBKDF2';
const encryptionAlg =
SubtleCrypto.supports?.('importKey', 'AES-OCB') ?
'AES-OCB' :
'AES-GCM';
const passwordKey = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
derivationAlg,
false,
['deriveKey'],
);
const nonce = crypto.getRandomValues(new Uint8Array(16));
const derivationParams =
derivationAlg === 'Argon2id' ?
{
nonce,
parallelism: 4,
memory: 2 ** 21,
passes: 1,
} :
{
salt: nonce,
iterations: 100_000,
hash: 'SHA-256',
};
const key = await crypto.subtle.deriveKey(
{
name: derivationAlg,
...derivationParams,
},
passwordKey,
{
name: encryptionAlg,
length: 256,
},
false,
['encrypt', 'decrypt'],
);
const plaintext = 'Hello, world!';
const iv = crypto.getRandomValues(new Uint8Array(16));
const encrypted = await crypto.subtle.encrypt(
{ name: encryptionAlg, iv },
key,
new TextEncoder().encode(plaintext),
);
const decrypted = new TextDecoder().decode(await crypto.subtle.decrypt(
{ name: encryptionAlg, iv },
key,
encrypted,
));
```

## Algorithm matrix

The table details the algorithms supported by the Node.js Web Crypto API
Expand Down Expand Up @@ -550,6 +617,28 @@ added: v15.0.0
added: v15.0.0
-->

### Static method: `SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])`

> Stability: 1.0 - Early development. SubleCrypto.supports is an experimental
> implementation based on [Modern Algorithms in the Web Cryptography API][]

<!-- YAML
added: REPLACEME
-->

<!--lint disable maximum-line-length remark-lint-->

* `operation` {string} "encrypt", "decrypt", "sign", "verify", "digest", "generateKey", "deriveKey", "deriveBits", "importKey", "exportKey", "wrapKey", or "unwrapKey"
* `algorithm` {string|Algorithm|AesCbcParams|AesCtrParams|AesGcmParams|AesKeyGenParams|EcdhKeyDeriveParams|EcdsaParams|EcKeyGenParams|EcKeyImportParams|Ed448Params|HkdfParams|HmacImportParams|HmacKeyGenParams|Pbkdf2Params|RsaHashedImportParams|RsaHashedKeyGenParams|RsaOaepParams|RsaPssParams}
* `lengthOrAdditionalAlgorithm` {null|number|string|Algorithm|AesCbcParams|AesCtrParams|AesDerivedKeyParams|AesGcmParams|AesKeyGenParams|EcdhKeyDeriveParams|EcdsaParams|EcKeyGenParams|EcKeyImportParams|Ed448Params|HkdfParams|HmacImportParams|HmacKeyGenParams|Pbkdf2Params|RsaHashedImportParams|RsaHashedKeyGenParams|RsaOaepParams|RsaPssParams} Depending on the operation this is either ignored, the value of the length argument when operation is "deriveBits", the algorithm of key to be derived when operation is "deriveKey", the algorithm of key to be exported before wrapping when operation is "wrapKey", or the algorithm of key to be imported after unwrapping when operation is "unwrapKey". **Default:** `null` when operation is "deriveBits", `undefined` otherwise.
* Returns: {boolean} Indicating whether the implementation supports the given operation

<!--lint enable maximum-line-length remark-lint-->

Allows feature detection in Web Crypto API, which can be used to detect whether
a given algorithm identifier (including any of its parameters) is supported for
the given operation.

### `subtle.decrypt(algorithm, key, data)`

<!-- YAML
Expand Down Expand Up @@ -1808,6 +1897,7 @@ The length (in bytes) of the random salt to use.

[JSON Web Key]: https://tools.ietf.org/html/rfc7517
[Key usages]: #cryptokeyusages
[Modern Algorithms in the Web Cryptography API]: https://wicg.github.io/webcrypto-modern-algos/
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
[Secure Curves in the Web Cryptography API]: https://wicg.github.io/webcrypto-secure-curves/
Expand Down
1 change: 1 addition & 0 deletions lib/internal/crypto/hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@ module.exports = {
hkdf,
hkdfSync,
hkdfDeriveBits,
validateHkdfDeriveBitsLength,
};
1 change: 1 addition & 0 deletions lib/internal/crypto/pbkdf2.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,5 @@ module.exports = {
pbkdf2,
pbkdf2Sync,
pbkdf2DeriveBits,
validatePbkdf2DeriveBitsLength,
};
147 changes: 147 additions & 0 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const {
} = require('internal/crypto/util');

const {
emitExperimentalWarning,
kEnumerableProperty,
lazyDOMException,
} = require('internal/util');
Expand Down Expand Up @@ -923,7 +924,153 @@ class SubtleCrypto {
constructor() {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}

static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment here that explains the heuristics or points to the spec would be good as a reminder that this is implementing to a spec.

emitExperimentalWarning('The supports Web Crypto API method');
if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');
webidl ??= require('internal/crypto/webidl');
const prefix = "Failed to execute 'supports' on 'SubtleCrypto'";
webidl.requiredArguments(arguments.length, 2, { prefix });

operation = webidl.converters.DOMString(operation, {
prefix,
context: '1st argument',
});
algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
prefix,
context: '2nd argument',
});

switch (operation) {
case 'encrypt':
case 'decrypt':
case 'sign':
case 'verify':
case 'digest':
case 'generateKey':
case 'deriveKey':
case 'deriveBits':
case 'importKey':
case 'exportKey':
case 'wrapKey':
case 'unwrapKey':
break;
default:
return false;
}

let length;
let additionalAlgorithm;
if (operation === 'deriveKey') {
additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
prefix,
context: '3rd argument',
});

if (!check('importKey', additionalAlgorithm)) {
return false;
}

try {
length = getKeyLength(normalizeAlgorithm(additionalAlgorithm, 'get key length'));
} catch {
return false;
}

operation = 'deriveBits';
} else if (operation === 'wrapKey') {
additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
prefix,
context: '3rd argument',
});

if (!check('exportKey', additionalAlgorithm)) {
return false;
}
} else if (operation === 'unwrapKey') {
additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
prefix,
context: '3rd argument',
});

if (!check('importKey', additionalAlgorithm)) {
return false;
}
} else if (operation === 'deriveBits') {
length = lengthOrAdditionalAlgorithm;
if (length !== null) {
length = webidl.converters['unsigned long'](length, {
prefix,
context: '3rd argument',
});
}
}

return check(operation, algorithm, length);
}
}

function check(op, alg, length) {
let normalizedAlgorithm;
try {
normalizedAlgorithm = normalizeAlgorithm(alg, op);
} catch {
if (op === 'wrapKey') {
return check('encrypt', alg);
}

if (op === 'unwrapKey') {
return check('decrypt', alg);
}

return false;
}

switch (op) {
case 'encrypt':
case 'decrypt':
case 'sign':
case 'verify':
case 'digest':
case 'generateKey':
case 'importKey':
case 'exportKey':
case 'wrapKey':
case 'unwrapKey':
return true;
case 'deriveBits': {
if (normalizedAlgorithm.name === 'HKDF') {
try {
require('internal/crypto/hkdf').validateHkdfDeriveBitsLength(length);
} catch {
return false;
}
}

if (normalizedAlgorithm.name === 'PBKDF2') {
try {
require('internal/crypto/pbkdf2').validatePbkdf2DeriveBitsLength(length);
} catch {
return false;
}
}

return true;
}
case 'get key length':
try {
getKeyLength(alg);
return true;
} catch {
return false;
}
default: {
const assert = require('internal/assert');
assert.fail('Unreachable code');
}
}
}

const subtle = ReflectConstruct(function() {}, [], SubtleCrypto);

class Crypto {
Expand Down
Loading