| 1 | export 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 | |
| 38 | export 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 | } |