Subtle (WebCrypto)
W3C Web Cryptography API
The SubtleCrypto interface allows you to perform cryptographic operations using the standardized W3C Web Cryptography API.
Table of Contents
Theory
The WebCrypto API differs from the Node.js API in two main ways:
- Asynchronous Promises: All operations (
encrypt,verify, etc.) return Promises. This reflects the intense nature of crypto operations and prevents UI blocking. - Unextractable Keys: A
CryptoKeyobject is a handle to a key. If markedextractable: false, the raw key bytes can never be accessed by JavaScript code, protecting it from XSS attacks.
Module Methods
encrypt(algorithm, key, data)
Encrypts data.
Parameters:
Prop
Type
Supported algorithms: AES-CTR, AES-CBC, AES-GCM, AES-OCB, ChaCha20-Poly1305, RSA-OAEP
decrypt(algorithm, key, data)
Decrypts data. Supports the same algorithms as encrypt.
sign(algorithm, key, data)
Generates a digital signature.
Supported algorithms: ECDSA, Ed25519, Ed448, HMAC, KMAC128, KMAC256, ML-DSA-44, ML-DSA-65, ML-DSA-87, RSA-PSS, RSASSA-PKCS1-v1_5
verify(algorithm, key, signature, data)
Verifies a digital signature. Returns true or false. Supports the same algorithms as sign.
digest(algorithm, data)
Generates a hash digest.
Supported algorithms: SHA-1, SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512, cSHAKE128, cSHAKE256
cSHAKE128 and cSHAKE256 provide SHAKE XOF (Extendable Output Function) support. The length parameter (in bytes) is required to specify the output length.
// SHA3-256
const hash = await subtle.digest('SHA3-256', data);
// cSHAKE256 with custom output length
const xof = await subtle.digest(
{ name: 'cSHAKE256', length: 64 },
data
);generateKey(algorithm, extractable, keyUsages)
Generates a new key or key pair.
Example (RSA-OAEP):
const keyPair = await subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);importKey(format, keyData, algorithm, extractable, keyUsages)
Imports a key. Supported formats: raw, raw-secret, raw-public, raw-seed, pkcs8, spki, jwk.
exportKey(format, key)
Exports a key (if extractable). Supported formats match importKey.
deriveBits(algorithm, baseKey, length)
Derives raw bits from a key using a key derivation algorithm.
Parameters:
Prop
Type
Supported algorithms: PBKDF2, HKDF, Argon2d, Argon2i, Argon2id, ECDH, X25519, X448
// PBKDF2
const passwordKey = await subtle.importKey(
'raw',
new TextEncoder().encode('password'),
'PBKDF2',
false,
['deriveBits']
);
const bits = await subtle.deriveBits(
{
name: 'PBKDF2',
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
hash: 'SHA-256'
},
passwordKey,
256
);// HKDF
const ikmKey = await subtle.importKey(
'raw',
masterSecret,
'HKDF',
false,
['deriveBits']
);
const derived = await subtle.deriveBits(
{
name: 'HKDF',
hash: 'SHA-256',
salt: new Uint8Array(32),
info: new TextEncoder().encode('session-key')
},
ikmKey,
256
);// X25519 ECDH
const aliceKey = await subtle.generateKey(
{ name: 'X25519' },
true,
['deriveBits']
);
const sharedBits = await subtle.deriveBits(
{ name: 'X25519', public: bobPublicKey },
aliceKey.privateKey,
256
);deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)
Derives a new CryptoKey from a base key. Same derivation algorithms as deriveBits, but produces a usable key directly.
// Derive an AES key from a password using PBKDF2
const aesKey = await subtle.deriveKey(
{
name: 'PBKDF2',
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
hash: 'SHA-256'
},
passwordKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);wrapKey(format, key, wrappingKey, wrapAlgo)
Exports a key and encrypts (wraps) it for safe transport.
Parameters:
Prop
Type
Wrapping algorithms: AES-KW, AES-CBC, AES-CTR, AES-GCM, AES-OCB, ChaCha20-Poly1305, RSA-OAEP
// Wrap an AES key with AES-KW
const wrappingKey = await subtle.generateKey(
{ name: 'AES-KW', length: 256 },
true,
['wrapKey', 'unwrapKey']
);
const keyToWrap = await subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
const wrapped = await subtle.wrapKey(
'raw',
keyToWrap,
wrappingKey,
{ name: 'AES-KW' }
);unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)
Decrypts (unwraps) a wrapped key and imports it.
const unwrapped = await subtle.unwrapKey(
'raw',
wrapped,
wrappingKey,
{ name: 'AES-KW' },
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);getPublicKey(key, keyUsages)
Extracts the public key from a private CryptoKey.
const keyPair = await subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign', 'verify']
);
const publicOnly = await subtle.getPublicKey(
keyPair.privateKey,
['verify']
);SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])
Static method that checks if a given operation/algorithm combination is supported.
const canSign = SubtleCrypto.supports('sign', 'Ed25519');
const canEncrypt = SubtleCrypto.supports('encrypt', 'AES-GCM', 256);Encapsulation (ML-KEM)
Post-quantum key encapsulation methods for ML-KEM. See the PQC page for full details.
encapsulateBits(algorithm, key)— Encapsulates and returns{ sharedSecret, ciphertext }encapsulateKey(algorithm, key, sharedKeyAlgo, extractable, usages)— Encapsulates and returns a derivedCryptoKeydecapsulateBits(algorithm, key, ciphertext)— Decapsulates to raw bitsdecapsulateKey(algorithm, key, ciphertext, sharedKeyAlgo, extractable, usages)— Decapsulates to aCryptoKey
Supported Algorithms
Encryption/Decryption
| Algorithm | Type | Notes |
|---|---|---|
AES-CBC | Symmetric | Block cipher, requires padding |
AES-CTR | Symmetric | Stream-like, counter mode |
AES-GCM | Symmetric | Recommended — authenticated encryption |
AES-OCB | Symmetric | Authenticated, faster than GCM |
ChaCha20-Poly1305 | Symmetric | Authenticated, fast on mobile |
RSA-OAEP | Asymmetric | For small payloads or key wrapping |
Signing/Verification
| Algorithm | Type | Notes |
|---|---|---|
ECDSA | Asymmetric | P-256, P-384, P-521, secp256k1 |
Ed25519 | Asymmetric | Fast, deterministic |
Ed448 | Asymmetric | Higher security Ed curve |
HMAC | Symmetric | Message authentication |
KMAC128 / KMAC256 | Symmetric | Keccak-based MAC |
ML-DSA-44/65/87 | Post-Quantum | FIPS 204 lattice signatures |
RSA-PSS | Asymmetric | Probabilistic RSA signatures |
RSASSA-PKCS1-v1_5 | Asymmetric | Legacy RSA signatures |
Key Derivation
| Algorithm | Input | Notes |
|---|---|---|
PBKDF2 | Password | Iterations-based, widely supported |
HKDF | Key material | Extract-and-Expand (RFC 5869) |
Argon2d/i/id | Password | Memory-hard, PHC winner |
ECDH | Key pair | P-256, P-384, P-521, secp256k1 |
X25519 | Key pair | Modern ECDH |
X448 | Key pair | Higher security ECDH |
Key Wrapping
| Algorithm | Notes |
|---|---|
AES-KW | RFC 3394, purpose-built key wrapping |
AES-GCM | Authenticated wrapping |
AES-CBC / AES-CTR / AES-OCB | Alternative wrapping |
ChaCha20-Poly1305 | Authenticated wrapping |
RSA-OAEP | Asymmetric wrapping |
Real-World Examples
End-to-End Encryption
Symmetric encryption with a random key.
import { subtle } from 'react-native-quick-crypto';
async function secureMessage(msg: string) {
// Generate Key
const key = await subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
// Encrypt
const iv = QuickCrypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(msg);
const ciphertext = await subtle.encrypt(
{ name: "AES-GCM", iv: iv },
key,
encoded
);
return { key, iv, ciphertext };
}JWT Verification
Importing a public key from a JWK to verify a token.
async function verifyToken(token: string, jwk: any) {
const pubKey = await subtle.importKey(
"jwk",
jwk,
{ name: "ECDSA", namedCurve: "P-256" },
false,
["verify"]
);
const [header, payload, sigBase64] = token.split('.');
const data = new TextEncoder().encode(`${header}.${payload}`);
const signature = Buffer.from(sigBase64, 'base64');
return await subtle.verify(
{ name: "ECDSA", hash: "SHA-256" },
pubKey,
signature,
data
);
}Password-Based Encryption (PBKDF2 + AES-GCM)
import { subtle } from 'react-native-quick-crypto';
async function encryptWithPassword(plaintext: string, password: string) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const passwordKey = await subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveKey']
);
const aesKey = await subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
passwordKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
const ciphertext = await subtle.encrypt(
{ name: 'AES-GCM', iv },
aesKey,
new TextEncoder().encode(plaintext)
);
return { ciphertext, salt, iv };
}