From 2efcd8e3466d28966fb0da3e288b093e866f9b86 Mon Sep 17 00:00:00 2001 From: Rick Ye Date: Sun, 13 Sep 2026 01:13:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(browser):=20node:sqlite=20=E7=9A=84=20BLOB?= =?UTF-8?q?=20=E6=98=AF=E8=A3=B8=20Uint8Array=EF=BC=8CCookie=20=E8=A7=A3?= =?UTF-8?q?=E5=AF=86=E5=89=8D=E5=BF=85=E9=A1=BB=E8=BD=AC=20Buffer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/browser/browser-profile.ts | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/main/browser/browser-profile.ts b/apps/desktop/src/main/browser/browser-profile.ts index 8d71f3e..2798ac2 100644 --- a/apps/desktop/src/main/browser/browser-profile.ts +++ b/apps/desktop/src/main/browser/browser-profile.ts @@ -206,13 +206,16 @@ function dpapiUnprotectKey(encodedKey: string): Promise { }) } -/** 解密单条 Chrome Cookie(v10/v11 均为 AES-256-GCM:3 字节版本 + 12 字节 nonce + 密文 + 16 字节 tag) */ -function decryptChromeBlob(encrypted: Buffer, key: Buffer): string { - const version = encrypted.subarray(0, 3).toString('utf8') +/** 解密单条 Chrome Cookie(v10/v11 均为 AES-256-GCM:3 字节版本 + 12 字节 nonce + 密文 + 16 字节 tag)。 + * 注意 node:sqlite 返回的 BLOB 是裸 Uint8Array 而非 Buffer——其 toString() 会忽略 + * 编码参数、返回逗号拼接的数字,必须先转成 Buffer 才能按 utf8 读版本头。 */ +function decryptChromeBlob(encrypted: Uint8Array, key: Buffer): string { + const blob = Buffer.isBuffer(encrypted) ? encrypted : Buffer.from(encrypted) + const version = blob.subarray(0, 3).toString('utf8') if (version !== 'v10' && version !== 'v11') throw new Error(`不支持的 Cookie 加密版本: ${version}`) - const nonce = encrypted.subarray(3, 15) - const tag = encrypted.subarray(encrypted.length - 16) - const data = encrypted.subarray(15, encrypted.length - 16) + const nonce = blob.subarray(3, 15) + const tag = blob.subarray(blob.length - 16) + const data = blob.subarray(15, blob.length - 16) const decipher = createDecipheriv('aes-256-gcm', key, nonce) decipher.setAuthTag(tag) return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8') @@ -284,7 +287,7 @@ function applyStagedLocalStorage(staging: string, targetPartitionDir: string): v } type SourceCookieRead = - | { kind: 'ok'; cookies: BrowserCookieSeed[]; skipped: number } + | { kind: 'ok'; cookies: BrowserCookieSeed[]; v20: number; undecryptable: number } | { kind: 'unsupported'; note: string } /** @@ -317,14 +320,16 @@ async function readSourceCookies(root: string, staging: string): Promise 3) { - value = decryptChromeBlob(r.encrypted_value, key) + const encrypted = Buffer.from(r.encrypted_value) + // v20 = 应用绑定加密(Chrome 127+ 逐步启用),第三方无法解密,单独计数便于如实提示 + if (encrypted.subarray(0, 3).toString('utf8') === 'v20') { + v20++ + continue + } + value = decryptChromeBlob(encrypted, key) } if (!value) { - skipped++ + undecryptable++ continue } cookies.push({ @@ -351,10 +362,10 @@ async function readSourceCookies(root: string, staging: string): Promise 0) cookieNote = `源 Cookie ${injected} 条已注入,${skipped} 条无法解密已跳过。` + const skipNotes: string[] = [] + if (sourceRead.v20 > 0) skipNotes.push(`${sourceRead.v20} 条为应用绑定加密(v20)无法迁移`) + const failedCount = result.failed + sourceRead.undecryptable + if (failedCount > 0) skipNotes.push(`${failedCount} 条解密失败已跳过`) + if (skipNotes.length > 0) cookieNote = `源 Cookie ${injected} 条已注入,${skipNotes.join(',')}。` } else { cookieNote = sourceRead.note }