From 5cf13f4396139766c7f8988e551d0c203d9c2e1a Mon Sep 17 00:00:00 2001 From: Rick Ye Date: Sun, 13 Sep 2026 00:43:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(browser):=20DPAPI=20=E8=A7=A3=E5=AF=86?= =?UTF-8?q?=E5=89=8D=E5=85=88=E5=89=A5=E6=8E=89=E8=A7=A3=E7=A0=81=E5=90=8E?= =?UTF-8?q?=205=20=E5=AD=97=E8=8A=82=20DPAPI=20=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/main/browser/browser-profile.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/main/browser/browser-profile.ts b/apps/desktop/src/main/browser/browser-profile.ts index 397001b..2ddbe81 100644 --- a/apps/desktop/src/main/browser/browser-profile.ts +++ b/apps/desktop/src/main/browser/browser-profile.ts @@ -171,9 +171,13 @@ function readSourceKey(userDataDir: string): SourceKeyInfo { } } -/** DPAPI 解出源浏览器的 AES 密钥(PowerShell 调 CryptUnprotectData;用户级,无需管理员) */ +/** DPAPI 解出源浏览器的 AES 密钥(PowerShell 调 CryptUnprotectData;用户级,无需管理员)。 + * Chromium 的 encrypted_key = base64("DPAPI" + 密文 blob):CryptUnprotectData 只接受 + * 剥掉 5 字节前缀后的 blob,前缀判断必须在解码后的字节上做——base64 文本本身 + * 永远不以 "DPAPI" 开头(实测带前缀调用必然 CryptographicException: 数据无效)。 */ function dpapiUnprotectKey(encodedKey: string): Promise { - const blob = encodedKey.startsWith('DPAPI') ? encodedKey.slice(5) : encodedKey + const bytes = Buffer.from(encodedKey, 'base64') + const blob = bytes.subarray(0, 5).toString('utf8') === 'DPAPI' ? bytes.subarray(5).toString('base64') : encodedKey const script = `$d=[Convert]::FromBase64String('${blob}');` + 'Add-Type -AssemblyName System.Security;' + @@ -183,9 +187,12 @@ function dpapiUnprotectKey(encodedKey: string): Promise { 'powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 20_000 }, - (err, stdout) => { + (err, stdout, stderr) => { if (err || !stdout.trim()) { - log.warn('dpapi unprotect failed', { err: err instanceof Error ? err.message : String(err ?? 'empty output') }) + log.warn('dpapi unprotect failed', { + err: err instanceof Error ? err.message : String(err ?? 'empty output'), + stderr: stderr ? String(stderr).slice(0, 200) : undefined + }) resolve(null) return }