- 拼多多进单 Cookie 失效即时拦截与告警修复(支持 43001 会话过期等错误识别) - 拼多多订单映射优先使用 out_sku_sn(外部商家编码)作为商品规格与工单匹配核心字段 - 新增 Chrome/Edge 浏览器扩展 (Manifest V3),支持端到端 RSA-OAEP + AES-256-GCM 信封加密自动保活 - 后台店铺管理新增插件配对面板、专属密钥管理与一键导入配置 JSON
128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
/**
|
|
* 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
|
|
}
|