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:

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:

  1. The JSG-generated stub receives the JavaScript call and forwards it to the C++ method
  2. The lookupAlgorithm function in src/workerd/api/crypto/crypto.c++ (lines 7-28) identifies the appropriate algorithm implementation
  3. The validateOperation function (lines 40-55) ensures key-algorithm compatibility
  4. A CryptoKey::Impl subclass executes the algorithm-specific logic using OpenSSL objects (EVP_*, RSA*, EC_KEY*)
  5. The implementation returns a jsg::BufferSource or CryptoKey back 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:

  1. Register the algorithm in src/workerd/api/crypto/crypto.c++ by adding an entry to the lookupAlgorithm static set
  2. Create a subclass of CryptoKey::Impl in the implementation headers (see src/workerd/api/crypto/impl.h) that implements required virtual methods: import, generate, encrypt, decrypt, sign, or verify
  3. Expose algorithm parameters by adding a new dictionary struct to the Web Crypto definitions if extra parameters are required
  4. Update the Node compatibility layer by adding static methods on CryptoImpl in src/workerd/api/node/crypto.c++ that forward to your new CryptoKey::Impl functions
  5. Write tests in src/workerd/api/tests/ (e.g., crypto-myalgo-test.wd-test) and verify Node compatibility in src/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++ and src/workerd/api/crypto/crypto.h
  • The JSG binding layer routes JavaScript calls to C++ implementations via lookupAlgorithm and CryptoKey::Impl subclasses
  • Web Crypto methods validate operations through validateOperation before 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::Impl subclassing, and comprehensive testing in the src/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →