js/globals/crypto

js/globals/crypto.ts

Web Crypto API global.

Implements a useful subset of the W3C Web Cryptography API and installs it as globalThis.crypto at import time; the CryptoKey class is installed as a global too. Application code never imports this module directly — it just uses crypto like it would in a browser:

Backed by internal:openssl (libcrypto via FFI). cryptoAvailable reports whether that backend loaded successfully; if OpenSSL is not installed, every method throws an informative error rather than crashing the process. Release CI should include at least one OpenSSL-enabled lane so the WebCrypto algorithm matrix and named error behavior are exercised rather than skipped.

Supported digest names are SHA-1, SHA-256, SHA-384, and SHA-512. Symmetric key import supports raw and JWK AES-GCM, AES-CBC, HMAC, PBKDF2, and HKDF keys; generated AES-CTR keys can be exported as raw or JWK metadata. Asymmetric import/export supports the RSA, ECDSA, ECDH, and Ed25519 formats covered by the focused crypto tests. AES-CTR encryption, AES-KW, full WPT coverage, and full WebCrypto algorithm parity are outside this release baseline. Unsupported algorithms and key formats reject with NotSupportedError, malformed key material rejects with DataError, key/type/usage mismatches reject with InvalidAccessError, and backend operation failures such as AES-GCM authentication failure reject with OperationError.

CryptoKey instances are structured-cloneable: structuredClone() copies symmetric key material and takes a fresh reference on asymmetric OpenSSL key handles, so cloned keys have independent lifetimes.

Example

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12));
const secret = new TextEncoder().encode('attack at dawn');
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, secret);
const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
new TextDecoder().decode(plaintext); // "attack at dawn"

W3C Web Cryptography API: https://www.w3.org/TR/WebCryptoAPI/

Types

type BufferSource = ArrayBuffer | ArrayBufferView

Bytes accepted by Web Crypto methods.

Callers may pass an ArrayBuffer directly or any typed-array/DataView view over an ArrayBuffer. Views are read from their own byte offset and length.

type KeyType = 'public' | 'private' | 'secret'

Web Crypto key kind.

Public and private keys are asymmetric key handles. Secret keys hold symmetric key material such as AES, HMAC, PBKDF2, or HKDF input bytes.

type KeyFormat = 'raw' | 'pkcs8' | 'spki' | 'jwk'

Key serialization format accepted by importKey(), exportKey(), wrapKey(), and unwrapKey().

type KeyUsage = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits' | 'wrapKey' | 'unwrapKey'

Operation a CryptoKey is allowed to perform.

SubtleCrypto checks key usages before performing operations and rejects mismatches with InvalidAccessError.

type AlgorithmIdentifier = string | Algorithm

Algorithm argument accepted by SubtleCrypto.

Interfaces

interface Algorithm {

Named Web Crypto algorithm descriptor.

Most SubtleCrypto methods accept either a string algorithm name or an object with name plus algorithm-specific fields such as iv, hash, salt, or namedCurve.

const iv = crypto.getRandomValues(new Uint8Array(12));
const alg: Algorithm = { name: 'AES-GCM', iv };
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt']);
await crypto.subtle.encrypt(alg, key as CryptoKey, new Uint8Array(4));

Properties

name: string

Algorithm name such as "AES-GCM", "HMAC", or "ECDSA". Names are matched case-insensitively.

interface KeyAlgorithm {

Algorithm metadata exposed on a CryptoKey.

Fields depend on the key algorithm. AES and HMAC keys expose length, HMAC and RSA keys expose hash, elliptic-curve keys expose namedCurve, and RSA keys expose modulus and exponent metadata.

const key = await crypto.subtle.generateKey({ name: 'HMAC', hash: 'SHA-256' }, true, ['sign', 'verify']);
(key as CryptoKey).algorithm.name;       // "HMAC"
(key as CryptoKey).algorithm.hash?.name; // "SHA-256"

Properties

name: string

Algorithm name the key was created for, e.g. "AES-GCM" or "ECDSA".

hash?: { name: string; }

Digest bound to the key at import/generation time (HMAC and RSA keys).

length?: number

Key length in bits (AES and HMAC keys).

namedCurve?: string

Curve name for EC keys: "P-256", "P-384", or "P-521".

modulusLength?: number

RSA modulus size in bits, e.g. 2048.

publicExponent?: Uint8Array

RSA public exponent as big-endian bytes; [1, 0, 1] is 65537.

interface JsonWebKey {

JSON Web Key object accepted by importKey("jwk", ...) and returned by exportKey("jwk", ...).

The active algorithm determines which fields are required. Symmetric keys use kty: "oct" with k; EC keys use kty: "EC" with crv, x, and y; RSA keys use kty: "RSA" with n and e; Ed25519 keys use kty: "OKP". All binary fields are base64url-encoded without padding.

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 128 }, true, ['encrypt']);
const jwk = await crypto.subtle.exportKey('jwk', key as CryptoKey) as JsonWebKey;
jwk.kty; // "oct"
jwk.alg; // "A128GCM"

Properties

kty?: string

Key type: "oct" (symmetric), "EC", "RSA", or "OKP" (Ed25519).

k?: string

Symmetric key bytes (kty: "oct").

crv?: string

Curve name for EC keys (P-256, P-384, P-521) or "Ed25519" for OKP.

x?: string

EC public x coordinate, or the Ed25519 public key for OKP keys.

y?: string

EC public y coordinate.

d?: string

Private key material: EC scalar, RSA private exponent, or Ed25519 seed. Present only on private keys.

alg?: string

JWA algorithm identifier such as "A256GCM" or "HS256". On import a mismatched alg rejects with DataError.

n?: string

RSA modulus.

e?: string

RSA public exponent.

p?: string

RSA first prime factor.

q?: string

RSA second prime factor.

dp?: string

RSA first CRT exponent (d mod (p-1)).

dq?: string

RSA second CRT exponent (d mod (q-1)).

qi?: string

RSA CRT coefficient (q^-1 mod p).

key_ops?: KeyUsage[]

Operations the key may perform. On import, requesting a usage not listed here rejects with InvalidAccessError.

ext?: boolean

Extractability flag mirrored from the exported key.

interface CryptoKeyPair {

Asymmetric key pair returned by generateKey() for RSA, ECDSA, ECDH, and Ed25519 algorithms.

const { privateKey, publicKey } = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']) as CryptoKeyPair;
const data = new TextEncoder().encode('message');
const sig = await crypto.subtle.sign('Ed25519', privateKey, data);
await crypto.subtle.verify('Ed25519', publicKey, sig, data); // true

Properties

privateKey: CryptoKey

Private key used for private-key operations such as decrypting, signing, or deriving bits.

publicKey: CryptoKey

Public key used for public-key operations such as encrypting or verifying.

interface SubtleCrypto {

Web Crypto cryptographic operation surface exposed as crypto.subtle.

Methods are asynchronous and reject with DOMException names used by the Web Crypto specification. Unsupported algorithms reject with NotSupportedError, malformed key material with DataError, key/usage mismatches with InvalidAccessError, and backend failures with OperationError. Every method rejects with a plain Error when the OpenSSL backend is unavailable (see cryptoAvailable).

const data = new TextEncoder().encode('hello');
const digest = await crypto.subtle.digest('SHA-256', data);
const key = await crypto.subtle.generateKey({ name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
const mac = await crypto.subtle.sign('HMAC', key as CryptoKey, data);
await crypto.subtle.verify('HMAC', key as CryptoKey, mac, data); // true

Methods

digest(algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>

Compute a digest of data.

Supported digest names are SHA-1, SHA-256, SHA-384, and SHA-512. The returned ArrayBuffer contains the raw digest bytes. Rejects with NotSupportedError for any other digest name.

const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('abc'));
const hex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
sign(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>

Sign data with key.

Supported algorithms are HMAC, ECDSA, RSA-PSS, RSASSA-PKCS1-v1_5, and Ed25519. The key must allow the sign usage and match the requested algorithm, otherwise the call rejects with InvalidAccessError.

ECDSA signatures are returned in the Web Crypto raw form — big-endian r ‖ s at twice the curve coordinate size — not ASN.1 DER. Ed25519 signatures are always 64 bytes. ECDSA requires hash on the algorithm object; HMAC and RSA use the hash bound to the key.

const pair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']) as CryptoKeyPair;
const sig = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, pair.privateKey, new Uint8Array(32));
new Uint8Array(sig).byteLength; // 64 (r ‖ s for P-256)
verify( algorithm: AlgorithmIdentifier, key: CryptoKey, signature: BufferSource, data: BufferSource ): Promise<boolean>

Verify signature for data with key.

Returns false for a valid algorithm/key combination with an invalid signature — including malformed or wrong-length ECDSA and Ed25519 signatures. Key or algorithm mismatches reject with InvalidAccessError instead. HMAC comparison is constant-time.

const key = await crypto.subtle.importKey('raw', new Uint8Array(32), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
const data = new TextEncoder().encode('payload');
const mac = await crypto.subtle.sign('HMAC', key, data);
await crypto.subtle.verify('HMAC', key, mac, data); // true
encrypt(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>

Encrypt data with key.

Supported algorithms are AES-GCM, AES-CBC, and RSA-OAEP. The key must allow the encrypt usage. AES requires iv on the algorithm object; the AES-GCM result is the ciphertext with the authentication tag appended (tagLength bits, default 128), and additionalData is authenticated when provided. RSA-OAEP requires a public key.

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12));
const box = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key as CryptoKey, new TextEncoder().encode('hi'));
decrypt(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>

Decrypt data with key.

The inverse of encrypt(): AES-GCM input must be ciphertext with the authentication tag appended, and the same iv (plus additionalData, if any) must be supplied. Authentication or padding failures reject with OperationError. The key must allow the decrypt usage. RSA-OAEP requires a private key.

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']) as CryptoKey;
const iv = crypto.getRandomValues(new Uint8Array(12));
const box = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode('hi'));
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, box);
new TextDecoder().decode(plain); // "hi"
importKey( format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[] ): Promise<CryptoKey>

Import key material and return a CryptoKey.

Supported formats depend on the algorithm: raw covers AES-GCM/AES-CBC (128- or 256-bit), HMAC, PBKDF2, HKDF, and Ed25519 public keys; jwk covers symmetric (oct), EC, RSA, and Ed25519 (OKP) keys; pkcs8 imports EC, RSA, and Ed25519 private keys; spki imports EC, RSA, and Ed25519 public keys.

extractable controls whether future export and wrap operations may reveal key material — except PBKDF2 and HKDF keys, which are always non-extractable. Malformed key material rejects with DataError; a JWK whose key_ops does not cover the requested usages rejects with InvalidAccessError.

const secret = crypto.getRandomValues(new Uint8Array(32));
const key = await crypto.subtle.importKey('raw', secret, 'AES-GCM', false, ['encrypt', 'decrypt']);
exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>

Export key in the requested format.

jwk returns a JsonWebKey object; all other formats return an ArrayBuffer. raw exports symmetric key bytes and Ed25519 public keys, pkcs8 exports private keys, and spki exports EC, RSA, and Ed25519 public keys. Non-extractable keys reject with InvalidAccessError.

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt']);
const raw = await crypto.subtle.exportKey('raw', key as CryptoKey) as ArrayBuffer;
new Uint8Array(raw).byteLength; // 32
generateKey( algorithm: AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[] ): Promise<CryptoKey | CryptoKeyPair>

Generate a new key or key pair.

Symmetric algorithms (AES-GCM, AES-CBC, AES-CTR with length 128, 192, or 256; HMAC with a default length derived from its hash) return a single CryptoKey. Asymmetric algorithms (RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDSA, ECDH, Ed25519) return a CryptoKeyPair whose usages are split between the halves — e.g. sign goes to the private key and verify to the public key. Ed25519 public keys are always extractable. Empty or invalid usage lists reject with SyntaxError.

const pair = await crypto.subtle.generateKey(
  { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
  true,
  ['encrypt', 'decrypt'],
) as CryptoKeyPair;
pair.publicKey.usages; // ["encrypt"]
deriveBits( algorithm: AlgorithmIdentifier, baseKey: CryptoKey, length?: number | null ): Promise<ArrayBuffer>

Derive raw bits from baseKey.

Supported algorithms are ECDH, PBKDF2, and HKDF. length is measured in bits and must be a non-negative multiple of 8 for PBKDF2 and HKDF (OperationError otherwise). PBKDF2 requires salt and iterations; HKDF defaults salt and info to empty. For ECDH, algorithm.public carries the peer's public key, both keys must be on the same curve, and a null/omitted length yields the full shared secret; non-byte-aligned lengths are masked down to the requested bit count.

const password = await crypto.subtle.importKey('raw', new TextEncoder().encode('hunter2'), 'PBKDF2', false, ['deriveBits']);
const salt = crypto.getRandomValues(new Uint8Array(16));
const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' }, password, 256);
deriveKey( algorithm: AlgorithmIdentifier, baseKey: CryptoKey, derivedKeyType: AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[] ): Promise<CryptoKey>

Derive a new CryptoKey from baseKey.

Equivalent to deriveBits() followed by importKey('raw', ...): the derivation algorithm (ECDH, PBKDF2, or HKDF) produces the key material and derivedKeyType decides its shape. Supported derived key types are AES-GCM, AES-CBC (default 256-bit), and HMAC (default length from its hash). The base key must allow deriveKey or deriveBits.

const password = await crypto.subtle.importKey('raw', new TextEncoder().encode('hunter2'), 'PBKDF2', false, ['deriveKey']);
const salt = crypto.getRandomValues(new Uint8Array(16));
const aes = await crypto.subtle.deriveKey(
  { name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' },
  password,
  { name: 'AES-GCM', length: 256 },
  false,
  ['encrypt', 'decrypt'],
);
wrapKey( format: KeyFormat, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: AlgorithmIdentifier ): Promise<ArrayBuffer>

Export and encrypt key with wrappingKey.

Exports key in format (jwk exports are serialized to JSON text first) and encrypts the result with AES-GCM or AES-CBC. The wrapping key must allow the wrapKey usage — the encrypt usage is not required — and the wrapped key must be extractable.

const kek = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['wrapKey', 'unwrapKey']) as CryptoKey;
const dek = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']) as CryptoKey;
const iv = crypto.getRandomValues(new Uint8Array(12));
const wrapped = await crypto.subtle.wrapKey('raw', dek, kek, { name: 'AES-GCM', iv });
unwrapKey( format: KeyFormat, wrappedKey: BufferSource, unwrappingKey: CryptoKey, unwrapAlgorithm: AlgorithmIdentifier, unwrappedKeyAlgorithm: AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[] ): Promise<CryptoKey>

Decrypt wrapped key material and import the result.

The inverse of wrapKey(): decrypts with AES-GCM or AES-CBC, then imports the plaintext in format — only raw and jwk are supported here. The unwrapping key must allow the unwrapKey usage; decryption failure rejects with OperationError.

// Continuing from the wrapKey() example above:
const dek = await crypto.subtle.unwrapKey('raw', wrapped, kek, { name: 'AES-GCM', iv }, 'AES-GCM', false, ['decrypt']);

interface Crypto {

The Web Crypto global object installed as globalThis.crypto.

Crypto provides synchronous random byte generation, random UUID creation, and the asynchronous subtle cryptography surface.

const nonce = crypto.getRandomValues(new Uint8Array(16));
const requestId = crypto.randomUUID();
const fingerprint = await crypto.subtle.digest('SHA-256', nonce);

Methods

getRandomValues<T extends ArrayBufferView>(typedArray: T): T

Fill typedArray with cryptographically strong random bytes.

The same array object is returned. Only the region the view covers is filled, so sub-array views leave the rest of the backing buffer untouched.

Throws TypeMismatchError for non-integer typed arrays such as Float64Array, and QuotaExceededError for views larger than 65,536 bytes, matching the Web Crypto quota.

const iv = crypto.getRandomValues(new Uint8Array(12));
randomUUID(): string

Return a version 4 random UUID string.

Randomness comes from libcrypto via fino:uuid.

crypto.randomUUID(); // "8b4bd1ab-…-…-…-…"

Readonly Properties

readonly subtle: SubtleCrypto

Asynchronous Web Crypto operation surface.

Classes

class CryptoKey {

Web Crypto key handle backed by either symmetric bytes or an OpenSSL EVP_PKEY.

CryptoKey exposes metadata but not key material unless the key is extractable and exported through SubtleCrypto. Asymmetric native keys are freed with a FinalizationRegistry when the wrapper is collected.

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt']);
key.type; // "secret"

Getters

get type(): KeyType

Key kind: public, private, or secret.

const key = await crypto.subtle.importKey('raw', new Uint8Array(16), 'AES-GCM', true, ['encrypt']);
key.type; // "secret"
get extractable(): boolean

Whether exportKey() and wrapKey() are allowed to reveal this key.

Non-extractable keys throw when exported or wrapped.

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 128 }, false, ['encrypt']);
key.extractable; // false
get algorithm(): KeyAlgorithm

Normalized algorithm descriptor associated with this key.

The descriptor includes fields such as hash, length, namedCurve, or RSA modulus information depending on the algorithm.

const key = await crypto.subtle.importKey('raw', new Uint8Array(16), 'AES-GCM', true, ['encrypt']);
key.algorithm.name; // "AES-GCM"
get usages(): readonly KeyUsage[]

Frozen allowed key usages.

Mutating the returned array is not possible.

const key = await crypto.subtle.importKey('raw', new Uint8Array(16), 'AES-GCM', true, ['encrypt']);
key.usages.includes('encrypt'); // true

Constants

const crypto: Crypto

Web Crypto global object backed by OpenSSL.

Methods throw an informative Error when libcrypto is unavailable. The object is installed on globalThis when this module is imported.

const bytes = crypto.getRandomValues(new Uint8Array(8));
const id = crypto.randomUUID();

Methods

getRandomValues<T extends ArrayBufferView>(typedArray: T): T

See Crypto.getRandomValues().

randomUUID(): string

See Crypto.randomUUID().

async digest(algorithm: string | { name: string; [key: string]: unknown; }, data: BufferSource): Promise<ArrayBuffer>

See SubtleCrypto.digest().

async sign(algorithm: string | { name: string; [key: string]: unknown; }, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>

See SubtleCrypto.sign().

async verify(algorithm: string | { name: string; [key: string]: unknown; }, key: CryptoKey, signature: BufferSource, data: BufferSource): Promise<boolean>

See SubtleCrypto.verify().

async encrypt(algorithm: string | { name: string; [key: string]: unknown; }, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>

See SubtleCrypto.encrypt().

async decrypt(algorithm: string | { name: string; [key: string]: unknown; }, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>

See SubtleCrypto.decrypt().

async importKey(format: KeyFormat, keyData: BufferSource, algorithm: string | { name: string; [key: string]: unknown; }, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>

See SubtleCrypto.importKey().

async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | object>

See SubtleCrypto.exportKey().

async generateKey(algorithm: string | { name: string; [key: string]: unknown; }, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey | { privateKey: CryptoKey; publicKey: CryptoKey; }>

See SubtleCrypto.generateKey().

async deriveBits(algorithm: string | { name: string; [key: string]: unknown; }, baseKey: CryptoKey, length?: number | null): Promise<ArrayBuffer>

See SubtleCrypto.deriveBits().

async deriveKey(algorithm: string | { name: string; [key: string]: unknown; }, baseKey: CryptoKey, derivedKeyType: string | { name: string; length?: number; [key: string]: unknown; }, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>

See SubtleCrypto.deriveKey().

async wrapKey(format: KeyFormat, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: string | { name: string; [k: string]: unknown; }): Promise<ArrayBuffer>

See SubtleCrypto.wrapKey().

async unwrapKey(format: KeyFormat, wrappedKey: BufferSource, unwrappingKey: CryptoKey, unwrapAlgorithm: string | { name: string; [k: string]: unknown; }, unwrappedKeyAlgorithm: string | { name: string; [k: string]: unknown; }, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>

See SubtleCrypto.unwrapKey().

Properties

subtle

See Crypto.subtle and the SubtleCrypto interface.

const cryptoAvailable

Whether the OpenSSL libcrypto backend loaded successfully.

When false, crypto methods throw instead of attempting unavailable FFI calls.

if (!cryptoAvailable) console.warn('crypto disabled');

const tlsAvailable

Whether the OpenSSL libssl TLS backend loaded successfully.

This flag is exported from the crypto globals module for code that wants to check TLS support alongside crypto support.

if (!tlsAvailable) console.warn('tls disabled');