Skip to main content
The Crypto API implements the Web Crypto standard for performing cryptographic operations. It provides secure, efficient implementations of hashing, encryption, signing, and key management.

Overview

The Crypto API provides:
  • Cryptographically secure random number generation
  • Hash functions (SHA-256, SHA-384, SHA-512, etc.)
  • Symmetric encryption (AES-GCM, AES-CBC, AES-CTR)
  • Asymmetric encryption (RSA-OAEP)
  • Digital signatures (RSA-PSS, ECDSA, EdDSA)
  • Key derivation (PBKDF2, HKDF)
  • Key generation and management
Implementation: src/workerd/api/crypto/ directory

Random values

Generate cryptographically secure random values:
// Generate random bytes
const array = new Uint8Array(32);
crypto.getRandomValues(array);

// Generate random UUID
const uuid = crypto.randomUUID();
console.log(uuid); // "a7f32a8c-..."

Hashing

Compute message digests:
// Hash a string
const data = new TextEncoder().encode('Hello World');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);

// Convert to hex string
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-2 family
await crypto.subtle.digest('SHA-1', data);    // Not recommended
await crypto.subtle.digest('SHA-256', data);
await crypto.subtle.digest('SHA-384', data);
await crypto.subtle.digest('SHA-512', data);

// MD5 (not recommended for security)
await crypto.subtle.digest('MD5', data);
Implementation: src/workerd/api/crypto/digest.h and digest.c++

Symmetric encryption

AES-GCM

Encrypt and decrypt with authenticated encryption:
// Generate a key
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

// Encrypt
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
);

// Decrypt
const decrypted = await crypto.subtle.decrypt(
  { name: 'AES-GCM', iv },
  key,
  encrypted
);

const text = new TextDecoder().decode(decrypted);
console.log(text); // "Secret message"

AES-CBC

Block cipher mode with padding:
const key = await crypto.subtle.generateKey(
  { name: 'AES-CBC', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

const iv = crypto.getRandomValues(new Uint8Array(16));

const encrypted = await crypto.subtle.encrypt(
  { name: 'AES-CBC', iv },
  key,
  data
);

AES-CTR

Counter mode for stream encryption:
const key = await crypto.subtle.generateKey(
  { name: 'AES-CTR', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

const counter = crypto.getRandomValues(new Uint8Array(16));

const encrypted = await crypto.subtle.encrypt(
  { name: 'AES-CTR', counter, length: 64 },
  key,
  data
);
Implementation: src/workerd/api/crypto/aes.c++

Asymmetric encryption

RSA-OAEP

Public key encryption:
// Generate key pair
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'RSA-OAEP',
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256'
  },
  true,
  ['encrypt', 'decrypt']
);

// Encrypt with public key
const encrypted = await crypto.subtle.encrypt(
  { name: 'RSA-OAEP' },
  keyPair.publicKey,
  data
);

// Decrypt with private key
const decrypted = await crypto.subtle.decrypt(
  { name: 'RSA-OAEP' },
  keyPair.privateKey,
  encrypted
);
Implementation: src/workerd/api/crypto/rsa.h and rsa.c++

Digital signatures

ECDSA

Elliptic curve signatures:
// Generate key pair
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'ECDSA',
    namedCurve: 'P-256'
  },
  true,
  ['sign', 'verify']
);

// Sign
const signature = await crypto.subtle.sign(
  {
    name: 'ECDSA',
    hash: 'SHA-256'
  },
  keyPair.privateKey,
  data
);

// Verify
const valid = await crypto.subtle.verify(
  {
    name: 'ECDSA',
    hash: 'SHA-256'
  },
  keyPair.publicKey,
  signature,
  data
);

console.log('Valid:', valid);

EdDSA

Edwards curve signatures:
// Generate Ed25519 key pair
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'Ed25519'
  },
  true,
  ['sign', 'verify']
);

// Sign
const signature = await crypto.subtle.sign(
  { name: 'Ed25519' },
  keyPair.privateKey,
  data
);

// Verify
const valid = await crypto.subtle.verify(
  { name: 'Ed25519' },
  keyPair.publicKey,
  signature,
  data
);

RSA-PSS

RSA probabilistic signature scheme:
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'RSA-PSS',
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256'
  },
  true,
  ['sign', 'verify']
);

const signature = await crypto.subtle.sign(
  {
    name: 'RSA-PSS',
    saltLength: 32
  },
  keyPair.privateKey,
  data
);
Implementation: src/workerd/api/crypto/ec.h and ec.c++

Key derivation

PBKDF2

Password-based key derivation:
// Import password as key material
const password = new TextEncoder().encode('password');
const keyMaterial = await crypto.subtle.importKey(
  'raw',
  password,
  'PBKDF2',
  false,
  ['deriveBits', 'deriveKey']
);

// Derive key
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = await crypto.subtle.deriveKey(
  {
    name: 'PBKDF2',
    salt,
    iterations: 100000,
    hash: 'SHA-256'
  },
  keyMaterial,
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

HKDF

HMAC-based key derivation:
const keyMaterial = await crypto.subtle.importKey(
  'raw',
  inputKeyMaterial,
  'HKDF',
  false,
  ['deriveBits', 'deriveKey']
);

const key = await crypto.subtle.deriveKey(
  {
    name: 'HKDF',
    salt,
    info,
    hash: 'SHA-256'
  },
  keyMaterial,
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);
Implementation: src/workerd/api/crypto/pbkdf2.c++ and hkdf.c++

Key management

Generate keys

Generate cryptographic keys:
// Symmetric key
const aesKey = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

// Asymmetric key pair
const rsaKeyPair = await crypto.subtle.generateKey(
  {
    name: 'RSA-PSS',
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256'
  },
  true,
  ['sign', 'verify']
);

Export keys

Export keys in various formats:
// Export as JWK
const jwk = await crypto.subtle.exportKey('jwk', key);
console.log(jwk);

// Export as raw bytes
const raw = await crypto.subtle.exportKey('raw', key);

// Export as PKCS#8 (private key)
const pkcs8 = await crypto.subtle.exportKey('pkcs8', privateKey);

// Export as SPKI (public key)
const spki = await crypto.subtle.exportKey('spki', publicKey);

Import keys

Import keys from various formats:
// Import from JWK
const key = await crypto.subtle.importKey(
  'jwk',
  jwkObject,
  { name: 'AES-GCM' },
  true,
  ['encrypt', 'decrypt']
);

// Import from raw bytes
const key = await crypto.subtle.importKey(
  'raw',
  keyBytes,
  { name: 'AES-GCM' },
  true,
  ['encrypt', 'decrypt']
);

// Import from PKCS#8
const privateKey = await crypto.subtle.importKey(
  'pkcs8',
  pkcs8Bytes,
  { name: 'RSA-PSS', hash: 'SHA-256' },
  true,
  ['sign']
);
Implementation: src/workerd/api/crypto/keys.h and keys.c++, jwk.h and jwk.c++

DigestStream

Compute hashes from streams:
const digestStream = new DigestStream('SHA-256');

const writer = digestStream.getWriter();
await writer.write(new TextEncoder().encode('Hello '));
await writer.write(new TextEncoder().encode('World'));
await writer.close();

const digest = await digestStream.digest;
const hashHex = Array.from(new Uint8Array(digest))
  .map(b => b.toString(16).padStart(2, '0'))
  .join('');

Best practices

Prefer modern algorithms:
// Good: AES-GCM for encryption
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

// Avoid: MD5 or SHA-1 for security
Always generate new random values:
// Good: new random IV for each encryption
const iv = crypto.getRandomValues(new Uint8Array(12));

// Bad: reusing IVs
const iv = new Uint8Array(12); // All zeros!
Never hardcode keys in source code:
// Good: get keys from environment or secrets
const keyData = await env.SECRETS.get('encryption-key');
const key = await crypto.subtle.importKey(...);

// Bad: hardcoded key
const key = new Uint8Array([1, 2, 3, ...]);
Use recommended key sizes:
// Good: 256-bit AES
{ name: 'AES-GCM', length: 256 }

// Good: 2048-bit RSA minimum
{ name: 'RSA-OAEP', modulusLength: 2048 }

Implementation details

The Crypto API is implemented across multiple files in src/workerd/api/crypto/:
  • crypto.h / .c++ - Public API (SubtleCrypto, CryptoKey)
  • impl.h / .c++ - Internal base classes and algorithm dispatch
  • keys.h / .c++ - Key management and encoding
  • aes.c++ - AES algorithms (CBC, CTR, GCM, KW)
  • rsa.h / .c++ - RSA algorithms (OAEP, PSS, PKCS1)
  • ec.h / .c++ - Elliptic curve algorithms (ECDSA, ECDH, EdDSA)
  • digest.h / .c++ - Hash functions
  • kdf.h, pbkdf2.c++, hkdf.c++ - Key derivation
  • jwk.h / .c++ - JWK import/export
All operations use BoringSSL (Google’s OpenSSL fork) wrapped with the OSSLCALL() macro for error handling. Key usage tracking from src/workerd/api/crypto/crypto.h:28:
class CryptoKeyUsageSet {
  static constexpr CryptoKeyUsageSet encrypt();
  static constexpr CryptoKeyUsageSet decrypt();
  static constexpr CryptoKeyUsageSet sign();
  static constexpr CryptoKeyUsageSet verify();
  static constexpr CryptoKeyUsageSet deriveKey();
  static constexpr CryptoKeyUsageSet deriveBits();
  static constexpr CryptoKeyUsageSet wrapKey();
  static constexpr CryptoKeyUsageSet unwrapKey();
};

Build docs developers (and LLMs) love