export class AESECBCipher { constructor() { this._key = null; } get algorithm() { return { name: "AES-ECB" }; } static async importKey(key, _algorithm, extractable, keyUsages) { const cipher = new AESECBCipher; await cipher._importKey(key, extractable, keyUsages); return cipher; } async _importKey(key, extractable, keyUsages) { this._key = await window.crypto.subtle.importKey( "raw", key, {name: "AES-CBC"}, extractable, keyUsages); } async encrypt(_algorithm, plaintext) { const x = new Uint8Array(plaintext); if (x.length % 16 !== 0 || this._key === null) { return null; } const n = x.length / 16; for (let i = 0; i < n; i++) { const y = new Uint8Array(await window.crypto.subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16), }, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16); x.set(y, i * 16); } return x; } } export class AESEAXCipher { constructor() { this._rawKey = null; this._ctrKey = null; this._cbcKey = null; this._zeroBlock = new Uint8Array(16); this._prefixBlock0 = this._zeroBlock; this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); } get algorithm() { return { name: "AES-EAX" }; } async _encryptBlock(block) { const encrypted = await window.crypto.subtle.encrypt({ name: "AES-CBC", iv: this._zeroBlock, }, this._cbcKey, block); return new Uint8Array(encrypted).slice(0, 16); } async _initCMAC() { const k1 = await this._encryptBlock(this._zeroBlock); const k2 = new Uint8Array(16); const v = k1[0] >>> 6; for (let i = 0; i < 15; i++) { k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2); k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1); } const lut = [0x0, 0x87, 0x0e, 0x89]; k2[14] ^= v >>> 1; k2[15] = (k1[15] << 2) ^ lut[v]; k1[15] = (k1[15] << 1) ^ lut[v >> 1]; this._k1 = k1; this._k2 = k2; } async _encryptCTR(data, counter) { const encrypted = await window.crypto.subtle.encrypt({ name: "AES-CTR", counter: counter, length: 128 }, this._ctrKey, data); return new Uint8Array(encrypted); } async _decryptCTR(data, counter) { const decrypted = await window.crypto.subtle.decrypt({ name: "AES-CTR", counter: counter, length: 128 }, this._ctrKey, data); return new Uint8Array(decrypted); } async _computeCMAC(data, prefixBlock) { if (prefixBlock.length !== 16) { return null; } const n = Math.floor(data.length / 16); const m = Math.ceil(data.length / 16); const r = data.length - n * 16; const cbcData = new Uint8Array((m + 1) * 16); cbcData.set(prefixBlock); cbcData.set(data, 16); if (r === 0) { for (let i = 0; i < 16; i++) { cbcData[n * 16 + i] ^= this._k1[i]; } } else { cbcData[(n + 1) * 16 + r] = 0x80; for (let i = 0; i < 16; i++) { cbcData[(n + 1) * 16 + i] ^= this._k2[i]; } } let cbcEncrypted = await window.crypto.subtle.encrypt({ name: "AES-CBC", iv: this._zeroBlock, }, this._cbcKey, cbcData); cbcEncrypted = new Uint8Array(cbcEncrypted); const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16); return mac; } static async importKey(key, _algorithm, _extractable, _keyUsages) { const cipher = new AESEAXCipher; await cipher._importKey(key); return cipher; } async _importKey(key) { this._rawKey = key; this._ctrKey = await window.crypto.subtle.importKey( "raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]); this._cbcKey = await window.crypto.subtle.importKey( "raw", key, {name: "AES-CBC"}, false, ["encrypt"]); await this._initCMAC(); } async encrypt(algorithm, message) { const ad = algorithm.additionalData; const nonce = algorithm.iv; const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0); const encrypted = await this._encryptCTR(message, nCMAC); const adCMAC = await this._computeCMAC(ad, this._prefixBlock1); const mac = await this._computeCMAC(encrypted, this._prefixBlock2); for (let i = 0; i < 16; i++) { mac[i] ^= nCMAC[i] ^ adCMAC[i]; } const res = new Uint8Array(16 + encrypted.length); res.set(encrypted); res.set(mac, encrypted.length); return res; } async decrypt(algorithm, data) { const encrypted = data.slice(0, data.length - 16); const ad = algorithm.additionalData; const nonce = algorithm.iv; const mac = data.slice(data.length - 16); const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0); const adCMAC = await this._computeCMAC(ad, this._prefixBlock1); const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2); for (let i = 0; i < 16; i++) { computedMac[i] ^= nCMAC[i] ^ adCMAC[i]; } if (computedMac.length !== mac.length) { return null; } for (let i = 0; i < mac.length; i++) { if (computedMac[i] !== mac[i]) { return null; } } const res = await this._decryptCTR(encrypted, nCMAC); return res; } }