WebCrypto-backed async AES: GCM, CBC, and CTR modes via
crypto.subtle.
Module webcryptoAes | Source packages/front/fw/src/crypto/webcrypto/aes.js | Deps none | Worker-safe yes
Opt-in alternative to the pure-JS aes + mode/* modules. Async, Uint8Array / CryptoKey I/O. Every method resolves to Uint8Array | CryptoKey | false — never rejects. Key generation, import, and export are included. AES-KW key-wrapping is in the sibling webcryptoAesKw module.
Resolve
const webcryptoAes = runtime.resolve('webcryptoAes');
// Returns: { isAvailable, generateKey, importKey, exportKey,
// encryptGcm, decryptGcm, encryptCbc, decryptCbc,
// encryptCtr, decryptCtr }
API
| Method | Signature | Returns |
|---|---|---|
isAvailable |
() => boolean |
true when crypto.subtle is present |
generateKey |
(mode: 'GCM'|'CBC'|'CTR', lengthBits?: 128|192|256, extractable?: boolean) => Promise<CryptoKey|false> |
Fresh key or false |
importKey |
(raw: Uint8Array, mode: 'GCM'|'CBC'|'CTR', extractable?: boolean) => Promise<CryptoKey|false> |
Imported key or false |
exportKey |
(key: CryptoKey) => Promise<Uint8Array|false> |
Raw key bytes or false |
encryptGcm |
(key: CryptoKey, iv: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array, tagBits?: number) => Promise<Uint8Array|false> |
ciphertext || tag or false |
decryptGcm |
(key: CryptoKey, iv: Uint8Array, ctWithTag: Uint8Array, aad?: Uint8Array, tagBits?: number) => Promise<Uint8Array|false> |
Plaintext or false (auth failure or error) |
encryptCbc |
(key: CryptoKey, iv: Uint8Array, plaintext: Uint8Array) => Promise<Uint8Array|false> |
Ciphertext (PKCS#7 padded) or false |
decryptCbc |
(key: CryptoKey, iv: Uint8Array, ciphertext: Uint8Array) => Promise<Uint8Array|false> |
Plaintext or false |
encryptCtr |
(key: CryptoKey, counter: Uint8Array, counterBits: number, data: Uint8Array) => Promise<Uint8Array|false> |
Encrypted bytes or false |
decryptCtr |
(key: CryptoKey, counter: Uint8Array, counterBits: number, data: Uint8Array) => Promise<Uint8Array|false> |
Decrypted bytes or false |
Defaults: lengthBits = 256, extractable for generateKey = true, extractable for importKey = false, tagBits = 128.
IV / counter length requirements:
- GCM: any length; 12 bytes (96-bit) strongly recommended (NIST SP 800-38D §8.2).
- CBC: exactly 16 bytes.
- CTR: exactly 16 bytes;
counterBitstypically 64.
Validation errors log [crypto] INVALID: webcryptoAes: <reason> and resolve false:
- Unknown mode (not
GCM,CBC, orCTR). raw.lengthnot in{16, 24, 32}forimportKey.- Wrong IV / counter byte-length for CBC / CTR.
- Non-
Uint8Arraywhere aUint8Arrayis required.
Examples
AES-GCM encrypt / decrypt
const enc = (s) => new TextEncoder().encode(s);
const key = await webcryptoAes.generateKey('GCM'); // AES-256-GCM
const iv = crypto.getRandomValues(new Uint8Array(12)); // 12-byte random IV
const aad = enc('authenticated metadata');
const ctTag = await webcryptoAes.encryptGcm(key, iv, enc('hello'), aad);
// ctTag is Uint8Array: ciphertext || 16-byte tag
const plaintext = await webcryptoAes.decryptGcm(key, iv, ctTag, aad);
// plaintext is Uint8Array; false on tampered ciphertext/tag/aad
AES-CBC encrypt / decrypt
const key = await webcryptoAes.generateKey('CBC', 128);
const iv = crypto.getRandomValues(new Uint8Array(16)); // 16-byte IV
const ct = await webcryptoAes.encryptCbc(key, iv, new TextEncoder().encode('secret'));
// Platform applies PKCS#7 padding
const pt = await webcryptoAes.decryptCbc(key, iv, ct);
AES-CTR encrypt / decrypt
const key = await webcryptoAes.generateKey('CTR', 256);
const counter = crypto.getRandomValues(new Uint8Array(16)); // 16-byte counter block
const data = new TextEncoder().encode('stream data');
const ciphertext = await webcryptoAes.encryptCtr(key, counter, 64, data);
const recovered = await webcryptoAes.decryptCtr(key, counter, 64, ciphertext);
Import / export a raw key
const rawBytes = crypto.getRandomValues(new Uint8Array(32)); // 256-bit
const key = await webcryptoAes.importKey(rawBytes, 'GCM', true);
const exported = await webcryptoAes.exportKey(key); // Uint8Array matching rawBytes
Availability guard
if (!webcryptoAes.isAvailable()) {
// Fall back to pure-JS aes + gcm modules
}
Worker Usage
const worker = fw.createWorker(
function ({ libs, args }) {
const { key, ivHex, ctTagHex } = args;
const fromHex = (h) => Uint8Array.from(h.match(/../g), b => parseInt(b, 16));
libs.webcryptoAes.decryptGcm(key, fromHex(ivHex), fromHex(ctTagHex))
.then(self.postMessage);
},
{ dependencies: ['webcryptoAes'], args: { key, ivHex, ctTagHex } }
);
Notes
- GCM output is
ciphertext || tag: WebCrypto appends the authentication tag to the ciphertext. Pass the full concatenated buffer todecryptGcm. The tag length defaults to 128 bits (16 bytes). - 12-byte IV per message: NIST SP 800-38D strongly recommends a 96-bit (12-byte) random IV per encryption for best performance and security. Never reuse an IV with the same key under GCM.
- CBC uses platform PKCS#7 padding: the ciphertext is always a multiple of 16 bytes, even when the plaintext already aligns. The platform strips padding transparently on decryption.
- GCM auth failure resolves
false:decryptGcmcatches the rejection fromcrypto.subtle.decrypt(tampered ciphertext, wrong tag, wrong AAD) and resolvesfalse— it never rejects. Callers must check the return value before using the plaintext. - No-throw contract: all methods are
asyncand resolve to a result orfalse.crypto.subtlerejections are caught and logged ([crypto] FAIL: webcryptoAes.<method>: …); they never propagate. - Worker-safe:
crypto.subtleis available in Web Workers; this module has no DOM dependency and no main-thread closures.