WASM-loaded AES-CMAC (SP 800-38B / RFC 4493): MAC compute and verify over AES-128/192/256. Async,
Uint8Array, no-throw.
Module wasmCmac | Source packages/front/fw/src/crypto/wasm/cmac.js | Deps wasmRuntime | Worker-safe yes
AES-CMAC is not in WebCrypto; this WASM tier (or the pure-JS cmac module) is the only path. This wrapper loads the delivered @awacloud/fw-wasm-crypto cmac binary — own SP 800-38B framing (csrc/cmac/cmac.c) over the vendored constant-time BearSSL aes_ct64 block cipher — through wasmRuntime and exposes an async surface for MAC computation and constant-time verification.
The cmac target is scalar-only (simd:false in targets.json — the AES block is bitsliced ct64, not simd128). There is no cmac.simd.wasm; wasmCmac forces { variant: 'scalar' } when loading.
Resolve
const wasmCmac = runtime.resolve('wasmCmac');
// Returns: { isAvailable, mac, verify }
API
| Method | Signature | Returns |
|---|---|---|
isAvailable |
() => boolean |
true when WebAssembly is present (mirrors wasmRuntime) |
mac |
(key: Uint8Array, message: Uint8Array, tagLen?: number) => Promise<Uint8Array|false> |
The AES-CMAC tag (tagLen bytes, default 16), or false |
verify |
(key: Uint8Array, message: Uint8Array, tag: Uint8Array) => Promise<boolean> |
Recompute tag then constant-time compare against tag |
Parameter constraints
| Parameter | Constraint |
|---|---|
key |
Uint8Array, length 16 / 24 / 32 (AES-128 / 192 / 256) |
message |
Uint8Array, any byte length including 0 |
tagLen |
Integer in [1, 16], default 16 |
tag (verify) |
Uint8Array, length 1..16; verify recomputes mac(key, message, tag.length) |
mac/verify resolve false (never throw) when:
- a parameter is invalid (
[crypto] INVALID: …logged):keynot aUint8Array,key.length ∉ {16,24,32},messagenot aUint8Array,tagLen ∉ [1,16]; - the binary cannot load —
WebAssemblyunavailable, fetch/compile failure, or ABI mismatch ([crypto] FAIL: …logged bywasmRuntime.load); - the WASM call returns a non-zero status (
[crypto] FAIL: wasmCmac.mac: aes_cmac status <n>).
verify additionally returns false when tag is not a Uint8Array or when the constant-time comparison fails.
Examples
Compute an AES-128 CMAC tag
const key = new Uint8Array(16); // fill from crypto.getRandomValues
const message = new TextEncoder().encode('authenticated payload');
const tag = await wasmCmac.mac(key, message);
if (tag === false) {
// Invalid params or WASM unavailable — fall back to the pure-JS tier.
}
// tag is a 16-byte Uint8Array.
Truncated tag (SP 800-38B §6.4)
const tag8 = await wasmCmac.mac(key, message, 8); // 8-byte (64-bit) tag
Constant-time verify
const ok = await wasmCmac.verify(key, message, storedTag);
// true only if the recomputed tag matches storedTag byte-for-byte.
RFC 4493 §4 published example vectors (AES-128)
const rfcKey = Uint8Array.from('2b7e151628aed2a6abf7158809cf4f3c'.match(/../g).map(x => parseInt(x, 16)));
// Example 1: empty message → bb1d6929e95937287fa37d129b756746
const t1 = await wasmCmac.mac(rfcKey, new Uint8Array(0));
// Example 4: 64-byte message → 51f0bebf7e3b9d92fc49741779363cfe
const t4 = await wasmCmac.mac(rfcKey, msgBytes);
Worker Usage
// wasmCmac is worker-safe: all constants are inside factory() and the
// binary is fetched by wasmRuntime's package loader via import.meta.url.
const worker = fw.createWorker(() => {
const wasmCmac = runtime.resolve('wasmCmac');
self.onmessage = async ({ data }) => {
const { key, message } = data;
const tag = await wasmCmac.mac(key, message);
self.postMessage(tag);
};
});
Notes
- Not in WebCrypto.
crypto.subtlehas no AES-CMAC; this WASM tier (or the pure-JScmac) is the only path. - Scalar-only binary. The AES block is bitsliced constant-time ct64 (not simd128);
wasmCmacforces{ variant: 'scalar' }because there is nocmac.simd.wasm. - AES-128 / 192 / 256. Key length selects the AES variant; all three are supported by the same binary.
- Empty message is valid.
msgLen=0is a well-specified SP 800-38B case and is handled correctly by the WASM implementation. - Constant-time verify. The byte comparison scans the full tag without early exit; a length mismatch returns
falseimmediately (length is not secret). - Output is copied OUT of WASM linear memory into a fresh
Uint8Array(never a view that could alias reused memory). - No-throw contract. Every failure path resolves to
falseafter aconsole.error; nothing rejects.
See also
- crypto/mode/cmac — the pure-JS AES-CMAC reference (same algorithm, universal default)
- crypto/wasm/runtime — the shared WASM loader adapter