main
1export class AESECBCipher {
2 constructor() {
3 this._key = null;
4 }
5
6 get algorithm() {
7 return { name: "AES-ECB" };
8 }
9
10 static async importKey(key, _algorithm, extractable, keyUsages) {
11 const cipher = new AESECBCipher;
12 await cipher._importKey(key, extractable, keyUsages);
13 return cipher;
14 }
15
16 async _importKey(key, extractable, keyUsages) {
17 this._key = await window.crypto.subtle.importKey(
18 "raw", key, {name: "AES-CBC"}, extractable, keyUsages);
19 }
20
21 async encrypt(_algorithm, plaintext) {
22 const x = new Uint8Array(plaintext);
23 if (x.length % 16 !== 0 || this._key === null) {
24 return null;
25 }
26 const n = x.length / 16;
27 for (let i = 0; i < n; i++) {
28 const y = new Uint8Array(await window.crypto.subtle.encrypt({
29 name: "AES-CBC",
30 iv: new Uint8Array(16),
31 }, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
32 x.set(y, i * 16);
33 }
34 return x;
35 }
36}
37
38export class AESEAXCipher {
39 constructor() {
40 this._rawKey = null;
41 this._ctrKey = null;
42 this._cbcKey = null;
43 this._zeroBlock = new Uint8Array(16);
44 this._prefixBlock0 = this._zeroBlock;
45 this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
46 this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
47 }
48
49 get algorithm() {
50 return { name: "AES-EAX" };
51 }
52
53 async _encryptBlock(block) {
54 const encrypted = await window.crypto.subtle.encrypt({
55 name: "AES-CBC",
56 iv: this._zeroBlock,
57 }, this._cbcKey, block);
58 return new Uint8Array(encrypted).slice(0, 16);
59 }
60
61 async _initCMAC() {
62 const k1 = await this._encryptBlock(this._zeroBlock);
63 const k2 = new Uint8Array(16);
64 const v = k1[0] >>> 6;
65 for (let i = 0; i < 15; i++) {
66 k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
67 k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
68 }
69 const lut = [0x0, 0x87, 0x0e, 0x89];
70 k2[14] ^= v >>> 1;
71 k2[15] = (k1[15] << 2) ^ lut[v];
72 k1[15] = (k1[15] << 1) ^ lut[v >> 1];
73 this._k1 = k1;
74 this._k2 = k2;
75 }
76
77 async _encryptCTR(data, counter) {
78 const encrypted = await window.crypto.subtle.encrypt({
79 name: "AES-CTR",
80 counter: counter,
81 length: 128
82 }, this._ctrKey, data);
83 return new Uint8Array(encrypted);
84 }
85
86 async _decryptCTR(data, counter) {
87 const decrypted = await window.crypto.subtle.decrypt({
88 name: "AES-CTR",
89 counter: counter,
90 length: 128
91 }, this._ctrKey, data);
92 return new Uint8Array(decrypted);
93 }
94
95 async _computeCMAC(data, prefixBlock) {
96 if (prefixBlock.length !== 16) {
97 return null;
98 }
99 const n = Math.floor(data.length / 16);
100 const m = Math.ceil(data.length / 16);
101 const r = data.length - n * 16;
102 const cbcData = new Uint8Array((m + 1) * 16);
103 cbcData.set(prefixBlock);
104 cbcData.set(data, 16);
105 if (r === 0) {
106 for (let i = 0; i < 16; i++) {
107 cbcData[n * 16 + i] ^= this._k1[i];
108 }
109 } else {
110 cbcData[(n + 1) * 16 + r] = 0x80;
111 for (let i = 0; i < 16; i++) {
112 cbcData[(n + 1) * 16 + i] ^= this._k2[i];
113 }
114 }
115 let cbcEncrypted = await window.crypto.subtle.encrypt({
116 name: "AES-CBC",
117 iv: this._zeroBlock,
118 }, this._cbcKey, cbcData);
119
120 cbcEncrypted = new Uint8Array(cbcEncrypted);
121 const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
122 return mac;
123 }
124
125 static async importKey(key, _algorithm, _extractable, _keyUsages) {
126 const cipher = new AESEAXCipher;
127 await cipher._importKey(key);
128 return cipher;
129 }
130
131 async _importKey(key) {
132 this._rawKey = key;
133 this._ctrKey = await window.crypto.subtle.importKey(
134 "raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
135 this._cbcKey = await window.crypto.subtle.importKey(
136 "raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
137 await this._initCMAC();
138 }
139
140 async encrypt(algorithm, message) {
141 const ad = algorithm.additionalData;
142 const nonce = algorithm.iv;
143 const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
144 const encrypted = await this._encryptCTR(message, nCMAC);
145 const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
146 const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
147 for (let i = 0; i < 16; i++) {
148 mac[i] ^= nCMAC[i] ^ adCMAC[i];
149 }
150 const res = new Uint8Array(16 + encrypted.length);
151 res.set(encrypted);
152 res.set(mac, encrypted.length);
153 return res;
154 }
155
156 async decrypt(algorithm, data) {
157 const encrypted = data.slice(0, data.length - 16);
158 const ad = algorithm.additionalData;
159 const nonce = algorithm.iv;
160 const mac = data.slice(data.length - 16);
161 const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
162 const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
163 const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
164 for (let i = 0; i < 16; i++) {
165 computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
166 }
167 if (computedMac.length !== mac.length) {
168 return null;
169 }
170 for (let i = 0; i < mac.length; i++) {
171 if (computedMac[i] !== mac[i]) {
172 return null;
173 }
174 }
175 const res = await this._decryptCTR(encrypted, nCMAC);
176 return res;
177 }
178}