/** * Pinduoduo Extension Web Crypto Envelope Encryption Module * 基于浏览器原生 Web Crypto API 的端到端信封加密工具库(无任何第三方依赖) */ // 将 Base64 字符串转换为 ArrayBuffer function base64ToArrayBuffer(base64) { const binaryString = atob(base64.replace(/\s+/g, '')) const len = binaryString.length const bytes = new Uint8Array(len) for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i) } return bytes.buffer } // 将 ArrayBuffer / Uint8Array 转换为 Base64 字符串 function arrayBufferToBase64(buffer) { let binary = '' const bytes = new Uint8Array(buffer) const len = bytes.byteLength for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]) } return btoa(binary) } // 导入服务端 RSA-OAEP (SHA-256) 公钥 (SPKI 格式) async function importRsaPublicKey(spkiBase64) { const cleanSpki = spkiBase64 .replace(/-----BEGIN PUBLIC KEY-----/, '') .replace(/-----END PUBLIC KEY-----/, '') .replace(/\s+/g, '') const keyBuffer = base64ToArrayBuffer(cleanSpki) return await crypto.subtle.importKey( 'spki', keyBuffer, { name: 'RSA-OAEP', hash: 'SHA-256', }, false, ['encrypt'], ) } // 计算文本 SHA-256 哈希(用于 Cookie 指纹比对) async function computeSha256(text) { const encoder = new TextEncoder() const data = encoder.encode(text) const hashBuffer = await crypto.subtle.digest('SHA-256', data) const hashArray = Array.from(new Uint8Array(hashBuffer)) return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') } /** * 执行混合信封加密 * 1. 生成一次性 256 位 AES-GCM 密钥 + 12 字节 IV * 2. 用 AES-256-GCM 加密明文载荷,分离密文与 128 位认证标签 (Tag) * 3. 用服务端 RSA 公钥加密 AES 密钥 * 4. 返回标准信封包 { key, iv, tag, cipher } */ async function encryptPayloadWithEnvelope(payload, serverPublicKeySpki) { const cryptoObj = self.crypto || window.crypto if (!cryptoObj || !cryptoObj.subtle) { throw new Error('当前浏览器环境不支持 Web Crypto API') } // 1. 生成一次性 AES-GCM 对称密钥 const aesKey = await cryptoObj.subtle.generateKey( { name: 'AES-GCM', length: 256, }, true, ['encrypt'], ) // 2. 生成 12 字节加密 IV const iv = cryptoObj.getRandomValues(new Uint8Array(12)) // 3. 使用 AES-256-GCM 加密明文 JSON const encoder = new TextEncoder() const plainBytes = encoder.encode(JSON.stringify(payload)) const encryptedBuf = await cryptoObj.subtle.encrypt( { name: 'AES-GCM', iv, tagLength: 128, }, aesKey, plainBytes, ) // Web Crypto AES-GCM 的输出格式为: [密文 (前 N 字节)] + [认证标签 Tag (后 16 字节)] const totalLen = encryptedBuf.byteLength const cipherBytes = encryptedBuf.slice(0, totalLen - 16) const tagBytes = encryptedBuf.slice(totalLen - 16) // 4. 导出对称密钥原始字节 (32 字节) const rawAesKey = await cryptoObj.subtle.exportKey('raw', aesKey) // 5. 导入服务端 RSA 公钥并加密 AES 密钥 const rsaKey = await importRsaPublicKey(serverPublicKeySpki) const encKeyBuf = await cryptoObj.subtle.encrypt( { name: 'RSA-OAEP', }, rsaKey, rawAesKey, ) return { key: arrayBufferToBase64(encKeyBuf), iv: arrayBufferToBase64(iv), tag: arrayBufferToBase64(tagBytes), cipher: arrayBufferToBase64(cipherBytes), } } // 导出全局函数供 background.js 与 popup.js 引用 if (typeof self !== 'undefined') { self.encryptPayloadWithEnvelope = encryptPayloadWithEnvelope self.computeSha256 = computeSha256 self.importRsaPublicKey = importRsaPublicKey }