Overview
Node.js provides an implementation of the Web Crypto API standard, offering browser-compatible cryptographic operations.
const { subtle } = globalThis.crypto;
(async function() {
const key = await subtle.generateKey({
name: 'HMAC',
hash: 'SHA-256',
length: 256,
}, true, ['sign', 'verify']);
const enc = new TextEncoder();
const message = enc.encode('I love cupcakes');
const digest = await subtle.sign({
name: 'HMAC',
}, key, message);
})();
Accessing Web Crypto
Access the Web Crypto API through:
globalThis.crypto (browser-compatible)
require('node:crypto').webcrypto
Modern Algorithms
Node.js supports modern cryptographic algorithms:
Symmetric Encryption
- AES-OCB - Authenticated encryption with associated data
- ChaCha20-Poly1305 - Modern authenticated encryption
Hashing
- SHA3-256, SHA3-384, SHA3-512 - SHA-3 family
- cSHAKE128, cSHAKE256 - Customizable SHAKE
Key Derivation
- Argon2d, Argon2i, Argon2id - Memory-hard password hashing
Post-Quantum Cryptography
- ML-DSA-44, ML-DSA-65, ML-DSA-87 - Post-quantum signatures
- ML-KEM-512, ML-KEM-768, ML-KEM-1024 - Post-quantum key encapsulation
MAC
- KMAC128, KMAC256 - Keccak-based MAC
Secure Curves
- Ed448, X448 - Edwards-curve operations
Class: Crypto
The global crypto object provides cryptographic functionality:
crypto.subtle
Access to the SubtleCrypto API for cryptographic operations:
const { subtle } = globalThis.crypto;
crypto.getRandomValues(typedArray)
Generate cryptographically strong random values:
const array = new Uint8Array(32);
globalThis.crypto.getRandomValues(array);
console.log(array);
Parameters:
typedArray - Integer-based TypedArray to fill with random values
Returns: Reference to the filled TypedArray
Maximum array size is 65,536 bytes. Float arrays are not supported.
crypto.randomUUID()
Generate RFC 4122 version 4 UUID:
const uuid = globalThis.crypto.randomUUID();
console.log(uuid);
// '36b8f84d-df4e-4d49-b662-bcde71a8764f'
Class: SubtleCrypto
Provides low-level cryptographic primitives:
SubtleCrypto.supports(operation, algorithm[, length])
Check runtime algorithm support:
const { SubtleCrypto } = globalThis;
if (SubtleCrypto.supports?.('importKey', 'Argon2id')) {
console.log('Argon2id is supported');
}
if (SubtleCrypto.supports?.('encrypt', 'AES-OCB')) {
console.log('AES-OCB is supported');
}
Parameters:
operation - Operation name (e.g., ‘encrypt’, ‘sign’)
algorithm - Algorithm identifier
length - Optional key length or additional algorithm parameter
Generating Keys
Generate symmetric or asymmetric keys:
AES Keys:
const key = await subtle.generateKey({
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt']);
ECDSA Key Pairs:
const { publicKey, privateKey } = await subtle.generateKey({
name: 'ECDSA',
namedCurve: 'P-521',
}, true, ['sign', 'verify']);
Ed25519 Key Pairs:
const keyPair = await subtle.generateKey({
name: 'Ed25519',
}, true, ['sign', 'verify']);
RSA Key Pairs:
const { publicKey, privateKey } = await subtle.generateKey({
name: 'RSA-PSS',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
}, true, ['sign', 'verify']);
HMAC Keys:
const key = await subtle.generateKey({
name: 'HMAC',
hash: 'SHA-512',
}, true, ['sign', 'verify']);
Encryption and Decryption
subtle.encrypt(algorithm, key, data)
Encrypt data:
const crypto = globalThis.crypto;
const key = await crypto.subtle.generateKey({
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12));
const data = new TextEncoder().encode('secret message');
const encrypted = await crypto.subtle.encrypt({
name: 'AES-GCM',
iv,
}, key, data);
subtle.decrypt(algorithm, key, data)
Decrypt data:
const decrypted = await crypto.subtle.decrypt({
name: 'AES-GCM',
iv,
}, key, encrypted);
const message = new TextDecoder().decode(decrypted);
Signing and Verification
subtle.sign(algorithm, key, data)
Sign data:
const { publicKey, privateKey } = await subtle.generateKey({
name: 'ECDSA',
namedCurve: 'P-384',
}, true, ['sign', 'verify']);
const data = new TextEncoder().encode('message to sign');
const signature = await subtle.sign({
name: 'ECDSA',
hash: 'SHA-384',
}, privateKey, data);
subtle.verify(algorithm, key, signature, data)
Verify signature:
const isValid = await subtle.verify({
name: 'ECDSA',
hash: 'SHA-384',
}, publicKey, signature, data);
console.log('Signature valid:', isValid);
Hashing
subtle.digest(algorithm, data)
Compute hash digest:
const data = new TextEncoder().encode('hello world');
const hashBuffer = await subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log(hashHex);
Supported hash algorithms:
'SHA-1' (not recommended for security)
'SHA-256'
'SHA-384'
'SHA-512'
'SHA3-256'
'SHA3-384'
'SHA3-512'
'cSHAKE128'
'cSHAKE256'
Key Derivation
subtle.deriveBits(algorithm, baseKey, length)
Derive bits from a key:
const password = new TextEncoder().encode('user-password');
const keyMaterial = await subtle.importKey(
'raw',
password,
'PBKDF2',
false,
['deriveBits']
);
const salt = crypto.getRandomValues(new Uint8Array(16));
const bits = await subtle.deriveBits({
name: 'PBKDF2',
hash: 'SHA-512',
salt,
iterations: 100000,
}, keyMaterial, 256);
Derive a key from another key:
const derivedKey = await subtle.deriveKey({
name: 'PBKDF2',
hash: 'SHA-512',
salt,
iterations: 100000,
}, keyMaterial, {
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt']);
Argon2 Example:
if (SubtleCrypto.supports?.('importKey', 'Argon2id')) {
const passwordKey = await crypto.subtle.importKey(
'raw-secret',
new TextEncoder().encode('password'),
'Argon2id',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey({
name: 'Argon2id',
nonce: crypto.getRandomValues(new Uint8Array(16)),
parallelism: 4,
memory: 2 ** 21,
passes: 1,
}, passwordKey, {
name: 'AES-GCM',
length: 256,
}, false, ['encrypt', 'decrypt']);
}
Key Import and Export
Import a key:
const jwk = {
kty: 'oct',
k: 'Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE',
alg: 'HS256',
ext: true,
};
const key = await subtle.importKey(
'jwk',
jwk,
{ name: 'HMAC', hash: 'SHA-256' },
true,
['sign', 'verify']
);
Supported formats:
'raw' - Raw binary key data
'pkcs8' - PKCS#8 private key
'spki' - SubjectPublicKeyInfo
'jwk' - JSON Web Key
'raw-public' - Raw public key (modern algorithms)
'raw-secret' - Raw secret key (modern algorithms)
'raw-seed' - Raw seed (modern algorithms)
Export a key:
const exportedKey = await subtle.exportKey('jwk', key);
console.log(JSON.stringify(exportedKey, null, 2));
Key Wrapping
Wrap (encrypt) a key:
const keyToWrap = await subtle.generateKey({
name: 'HMAC',
hash: 'SHA-512',
}, true, ['sign', 'verify']);
const wrappingKey = await subtle.generateKey({
name: 'AES-KW',
length: 256,
}, true, ['wrapKey', 'unwrapKey']);
const wrappedKey = await subtle.wrapKey(
'jwk',
keyToWrap,
wrappingKey,
'AES-KW'
);
Unwrap (decrypt) a key:
const unwrappedKey = await subtle.unwrapKey(
'jwk',
wrappedKey,
wrappingKey,
'AES-KW',
{ name: 'HMAC', hash: 'SHA-512' },
true,
['sign', 'verify']
);
Key Encapsulation (Post-Quantum)
subtle.encapsulateBits(algorithm, publicKey, length)
Encapsulate random bits using ML-KEM:
const { publicKey, privateKey } = await subtle.generateKey({
name: 'ML-KEM-768',
}, true, ['encapsulateBits', 'decapsulateBits']);
const { sharedSecret, ciphertext } = await subtle.encapsulateBits(
{ name: 'ML-KEM-768' },
publicKey,
256
);
subtle.decapsulateBits(algorithm, privateKey, ciphertext, length)
Decapsulate shared secret:
const decapsulatedSecret = await subtle.decapsulateBits(
{ name: 'ML-KEM-768' },
privateKey,
ciphertext,
256
);
subtle.getPublicKey(privateKey)
Extract public key from private key:
const publicKey = await subtle.getPublicKey(privateKey);
Class: CryptoKey
Represents a cryptographic key:
Properties
cryptoKey.type
'secret' - Symmetric key
'private' - Private asymmetric key
'public' - Public asymmetric key
Boolean indicating if key can be exported
cryptoKey.algorithm
Object describing the key’s algorithm and parameters
cryptoKey.usages
Array of permitted operations:
'encrypt', 'decrypt'
'sign', 'verify'
'deriveKey', 'deriveBits'
'wrapKey', 'unwrapKey'
'encapsulateBits', 'decapsulateBits'
'encapsulateKey', 'decapsulateKey'
Supported Algorithms
Encryption Algorithms
| Algorithm | Key Generation | Encrypt/Decrypt |
|---|
| AES-CBC | ✓ | ✓ |
| AES-CTR | ✓ | ✓ |
| AES-GCM | ✓ | ✓ |
| AES-OCB | ✓ | ✓ |
| ChaCha20-Poly1305 | ✓ | ✓ |
| RSA-OAEP | ✓ | ✓ |
Signature Algorithms
| Algorithm | Key Generation | Sign/Verify |
|---|
| HMAC | ✓ | ✓ |
| KMAC128 | ✓ | ✓ |
| KMAC256 | ✓ | ✓ |
| RSASSA-PKCS1-v1_5 | ✓ | ✓ |
| RSA-PSS | ✓ | ✓ |
| ECDSA | ✓ | ✓ |
| Ed25519 | ✓ | ✓ |
| Ed448 | ✓ | ✓ |
| ML-DSA-44 | ✓ | ✓ |
| ML-DSA-65 | ✓ | ✓ |
| ML-DSA-87 | ✓ | ✓ |
Key Derivation Algorithms
| Algorithm | Import Key | Derive |
|---|
| PBKDF2 | ✓ | ✓ |
| HKDF | ✓ | ✓ |
| ECDH | ✓ | ✓ |
| X25519 | ✓ | ✓ |
| X448 | ✓ | ✓ |
| Argon2d | ✓ | ✓ |
| Argon2i | ✓ | ✓ |
| Argon2id | ✓ | ✓ |
Key Encapsulation
| Algorithm | Key Generation | Encapsulate/Decapsulate |
|---|
| ML-KEM-512 | ✓ | ✓ |
| ML-KEM-768 | ✓ | ✓ |
| ML-KEM-1024 | ✓ | ✓ |
Security Best Practices
SHA-1 is cryptographically broken. Use SHA-256 or stronger for security-critical applications.
For password hashing, prefer Argon2id when available, or PBKDF2 with at least 100,000 iterations.
Always use unique initialization vectors (IVs) for each encryption operation. Never reuse IVs with the same key.
Browser Compatibility
The Web Crypto API implementation in Node.js aims for compatibility with browser implementations, making it easier to share cryptographic code between server and client.
- Crypto - Node.js native cryptography
- TLS/SSL - Secure network communications
- Permissions - Control access to cryptographic operations