fix(browser): DPAPI 解密前先剥掉解码后 5 字节 DPAPI 前缀

This commit is contained in:
Rick Ye
2026-09-13 00:43:14 +08:00
parent 06a316b6d1
commit 5cf13f4396
@@ -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<Buffer | null> { function dpapiUnprotectKey(encodedKey: string): Promise<Buffer | null> {
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 = const script =
`$d=[Convert]::FromBase64String('${blob}');` + `$d=[Convert]::FromBase64String('${blob}');` +
'Add-Type -AssemblyName System.Security;' + 'Add-Type -AssemblyName System.Security;' +
@@ -183,9 +187,12 @@ function dpapiUnprotectKey(encodedKey: string): Promise<Buffer | null> {
'powershell.exe', 'powershell.exe',
['-NoProfile', '-NonInteractive', '-Command', script], ['-NoProfile', '-NonInteractive', '-Command', script],
{ timeout: 20_000 }, { timeout: 20_000 },
(err, stdout) => { (err, stdout, stderr) => {
if (err || !stdout.trim()) { 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) resolve(null)
return return
} }