Files
order_site/apps/extensions/pdd-cookie-sync/crypto.js
T
yml2213 9fb27b0ad2 feat(pdd): 拼多多订单支持 out_sku_sn 匹配与浏览器插件加密保活
- 拼多多进单 Cookie 失效即时拦截与告警修复(支持 43001 会话过期等错误识别)
- 拼多多订单映射优先使用 out_sku_sn(外部商家编码)作为商品规格与工单匹配核心字段
- 新增 Chrome/Edge 浏览器扩展 (Manifest V3),支持端到端 RSA-OAEP + AES-256-GCM 信封加密自动保活
- 后台店铺管理新增插件配对面板、专属密钥管理与一键导入配置 JSON
2026-09-14 12:24:48 +08:00

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
}