1. 场景
浏览器原生支持了AES-GCM。
参考资料:https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt
2. 代码
/** * AES-GCM helper */ var aesGcmHelper = { _keyMap: new Map(), getIv(keyRaw) { if (this._keyMap.has(keyRaw)) { return this._keyMap.get(keyRaw).iv; } let iv = crypto.getRandomValues(new Uint8Array(12)); this._keyMap.set(keyRaw, Object.assign({}, this._keyMap.get(keyRaw), { iv })); return iv; }, /** * hex to ArrayBuffer * @param {String} hex */ hex2Buf(hex) { let buf = new Uint8Array( hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16); }) ); return buf; }, /** * base64 to ArrayBuffer * @param {String} hex */ base64ToBuf(base64) { var binaryString = window.atob(base64); var bytes = new Uint8Array(binaryString.length); for (var i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; }, /** * ArrayBuffer to hex * @param {ArrayBuffer} buffer */ buf2Hex(buffer) { let binary = [...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, '0')).join(''); return binary; }, /** * ArrayBuffer to base64 * @param {ArrayBuffer} buffer */ buf2Base64(buffer) { let binary = [...new Uint8Array(buffer)].map((x) => String.fromCharCode(x)).join(''); return window.btoa(binary); }, /** * encrypt * @param {String} data data * @param {String} keyRaw key raw * @param {Uint8Array} iv iv * @return {Promise} */ async encrypt(data, keyRaw, iv = null) { if (iv == null) { iv = this.getIv(keyRaw); } else { this._keyMap.set(keyRaw, Object.assign({}, this._keyMap.get(keyRaw), { iv })); } let encoded = new TextEncoder().encode(data); let key = await crypto.subtle.importKey('raw', new TextEncoder().encode(keyRaw), 'AES-GCM', true, ['encrypt', 'decrypt']); let ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded); return ciphertext; }, /** * decrypt * @param {ArrayBuffer} encrypted encrypted data * @param {String} keyRaw key raw * @param {Uint8Array} iv iv * @return {Promise} */ async decrypt(encrypted, keyRaw, iv = null) { if (iv == null) { iv = this.getIv(keyRaw); } if (!ArrayBuffer.isView(encrypted) && toString.apply(encrypted) != '[object ArrayBuffer]') { throw new Error('encrypted must is ArrayBuffer'); } let key = await crypto.subtle.importKey('raw', new TextEncoder().encode(keyRaw), 'AES-GCM', true, ['encrypt', 'decrypt']); let decrypted = ''; try { decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted); } catch (error) { throw new Error(error); return ''; } let decoded = new TextDecoder().decode(decrypted); return decoded; } };
3. 示例
(async () => { let iv = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); let keyRaw = 'IHieUI!%9awQqszp'; // 1.encrypt test let ciphertext = await aesGcmHelper.encrypt('hello, Tom', keyRaw, iv); // - hex let ciphertextOfHex = aesGcmHelper.buf2Hex(ciphertext); console.log(ciphertextOfHex); // => b85320c50c256bc27dc1113a92a83614d60161f6af4d1ce237b0 // - base64 let ciphertextOfBase64 = aesGcmHelper.buf2Base64(ciphertext); console.log(ciphertextOfBase64); // => uFMgxQwla8J9wRE6kqg2FNYBYfavTRziN7A= // 2.decrypt test // - hex let decodedData = await aesGcmHelper.decrypt(aesGcmHelper.hex2Buf(ciphertextOfHex), keyRaw, iv); console.log(decodedData); // => hello, Tom // - base64 decodedData = await aesGcmHelper.decrypt(aesGcmHelper.base64ToBuf(ciphertextOfBase64), keyRaw, iv); console.log(decodedData); // => hello, Tom })();