How to Implement Crypto Operations in workerd: Web Crypto and Node.js Compatibility
workerd exposes cryptographic operations through both the standard Web Crypto API (crypto.subtle) and a Node.js-compatible layer (node:crypto), with both stacks sharing a common OpenSSL-based core implemented in src/workerd/api/crypto/crypto.c++.
The cloudflare/workerd repository provides a complete stack for implementing crypto operations in workerd, exposing standard Web Crypto interfaces alongside Node.js compatibility primitives. Whether you are hashing data with SHA-256, encrypting with AES-GCM, or extending the runtime with custom algorithms, understanding the underlying C++ architecture helps you leverage these APIs effectively.
Cryptographic Stacks in workerd
workerd provides two fully-featured cryptography stacks that share a common core wrapping OpenSSL primitives:
- Web Crypto API: Accessed via
crypto.subtlein JavaScript, implemented primarily insrc/workerd/api/crypto/crypto.handsrc/workerd/api/crypto/crypto.c++ - Node.js-compatible crypto: Accessed via
require('node:crypto'), implemented insrc/workerd/api/node/crypto.handsrc/workerd/api/node/crypto.c++
Both stacks expose functionality through the JSG binding layer (jsg::Object), routing JavaScript calls through generated stubs to C++ methods that delegate to algorithm-specific implementations.
Implementing Web Crypto Operations
The SubtleCrypto interface declared in src/workerd/api/crypto/crypto.h (lines 337-382) provides the standard Web Crypto methods including digest(), encrypt(), sign(), and generateKey(). Each method forwards to corresponding virtual methods on CryptoKey::Impl subclasses.
AES-GCM Encryption Example
// Generate an AES-GCM key
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true, // extractable
['encrypt', 'decrypt']
);
// Encrypt a message
const iv = crypto.getRandomValues(new Uint8Array(12));
const plaintext = new TextEncoder().encode('Hello, workerd!');
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
plaintext
);
// Decrypt
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
console.log(new TextDecoder().decode(decrypted)); // → Hello, workerd!
Internal Routing of Crypto Calls
When you invoke a Web Crypto method, workerd follows this execution path:
- The JSG-generated stub receives the JavaScript call and forwards it to the C++ method
- The
lookupAlgorithmfunction insrc/workerd/api/crypto/crypto.c++(lines 7-28) identifies the appropriate algorithm implementation - The
validateOperationfunction (lines 40-55) ensures key-algorithm compatibility - A
CryptoKey::Implsubclass executes the algorithm-specific logic using OpenSSL objects (EVP_*,RSA*,EC_KEY*) - The implementation returns a
jsg::BufferSourceorCryptoKeyback to JavaScript
Using Node.js Crypto Compatibility
The Node.js compatibility layer mirrors Web Crypto primitives but follows Node.js method signatures. According to src/workerd/api/node/crypto.h (lines 14-52), this API provides methods like crypto.createHash(), crypto.randomBytes(), and crypto.pbkdf2(). Key export and import helpers reside in src/workerd/api/node/crypto-keys.c++.
Hashing and Key Derivation Example
import crypto from 'node:crypto';
// One-shot SHA-256 hash
const hash = crypto.createHash('sha256')
.update('Hello, workerd!')
.digest('hex');
console.log(hash); // 9b... (SHA-256 digest)
// PBKDF2 key derivation
crypto.pbkdf2(
'password',
'salt',
100_000,
32,
'sha256',
(err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex'));
}
);
Internally, these methods reuse the same core implementations from the Web Crypto stack. For example, HashHandle calls HashContext from the OpenSSL wrapper defined in the base crypto layer.
Adding Custom Cryptographic Algorithms
To implement a new algorithm (such as a post-quantum KEM) in workerd:
- Register the algorithm in
src/workerd/api/crypto/crypto.c++by adding an entry to thelookupAlgorithmstatic set - Create a subclass of
CryptoKey::Implin the implementation headers (seesrc/workerd/api/crypto/impl.h) that implements required virtual methods:import,generate,encrypt,decrypt,sign, orverify - Expose algorithm parameters by adding a new dictionary struct to the Web Crypto definitions if extra parameters are required
- Update the Node compatibility layer by adding static methods on
CryptoImplinsrc/workerd/api/node/crypto.c++that forward to your newCryptoKey::Implfunctions - Write tests in
src/workerd/api/tests/(e.g.,crypto-myalgo-test.wd-test) and verify Node compatibility insrc/workerd/api/node/tests/
Summary
- workerd exposes crypto operations through both the Web Crypto API (
crypto.subtle) and Node.js-compatible interfaces (node:crypto) - Both stacks share a common OpenSSL-based core implemented in
src/workerd/api/crypto/crypto.c++andsrc/workerd/api/crypto/crypto.h - The JSG binding layer routes JavaScript calls to C++ implementations via
lookupAlgorithmandCryptoKey::Implsubclasses - Web Crypto methods validate operations through
validateOperationbefore executing algorithm-specific logic - Node.js compatibility methods in
src/workerd/api/node/crypto.c++delegate to the same core implementations used by Web Crypto - New algorithms require registry updates,
CryptoKey::Implsubclassing, and comprehensive testing in thesrc/workerd/api/tests/directory
Frequently Asked Questions
How does workerd route Web Crypto API calls to OpenSSL?
When you call crypto.subtle.encrypt() or similar methods, workerd uses the JSG binding system to forward the call to C++. The lookupAlgorithm function in src/workerd/api/crypto/crypto.c++ maps the algorithm name to a specific implementation class derived from CryptoKey::Impl. This class then uses OpenSSL primitives (EVP_* functions) to perform the actual cryptographic operation and returns the result as a jsg::BufferSource.
What is the difference between Web Crypto and Node crypto in workerd?
The Web Crypto API (crypto.subtle) follows the W3C standard with Promise-based async methods and structured algorithm dictionaries. The Node.js compatibility layer (node:crypto) provides synchronous, callback-based APIs matching Node.js signatures like createHash() and pbkdf2(). Despite different interfaces, both layers ultimately call the same underlying C++ implementations in src/workerd/api/crypto/crypto.c++, ensuring consistent behavior across both APIs.
Can I add custom cryptographic algorithms to workerd?
Yes. To add a new algorithm, you must extend the algorithm registry in src/workerd/api/crypto/crypto.c++, implement a subclass of CryptoKey::Impl (defined in src/workerd/api/crypto/impl.h) with methods for key generation, import, and cryptographic operations, and expose the algorithm name in the Web Crypto dictionaries. You should also add corresponding methods to the Node compatibility layer in src/workerd/api/node/crypto.c++ and write tests in src/workerd/api/tests/.
Where are the cryptographic test suites located?
Web Crypto API tests reside in src/workerd/api/tests/ with files following the pattern crypto-*.wd-test. Node.js compatibility tests are located in src/workerd/api/node/tests/ with files named crypto_*.js. These test harnesses verify algorithm correctness, key import/export functionality, and compatibility with standard Web Crypto and Node.js behaviors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →