mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
fix: support Linux v11 cipher prefix for Chromium decryption (#571)
This commit is contained in:
+17
-1
@@ -5,17 +5,33 @@ package crypto
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/sha1"
|
||||
)
|
||||
|
||||
var chromiumCBCIV = bytes.Repeat([]byte{0x20}, aes.BlockSize)
|
||||
|
||||
// kEmptyKey is Chromium's decrypt-only fallback for data corrupted by a
|
||||
// KWallet race in Chrome ~89 (crbug.com/40055416). Matches the kEmptyKey
|
||||
// constant in os_crypt_linux.cc.
|
||||
var kEmptyKey = PBKDF2Key([]byte(""), []byte("saltysalt"), 1, 16, sha1.New)
|
||||
|
||||
const minCBCDataSize = versionPrefixLen + aes.BlockSize // "v10" + one AES block = 19 bytes minimum
|
||||
|
||||
func DecryptChromium(key, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext) < minCBCDataSize {
|
||||
return nil, errShortCiphertext
|
||||
}
|
||||
return AESCBCDecrypt(key, chromiumCBCIV, ciphertext[versionPrefixLen:])
|
||||
payload := ciphertext[versionPrefixLen:]
|
||||
|
||||
plaintext, err := AESCBCDecrypt(key, chromiumCBCIV, payload)
|
||||
if err == nil {
|
||||
return plaintext, nil
|
||||
}
|
||||
// Retry with kEmptyKey to recover crbug.com/40055416 data.
|
||||
if alt, altErr := AESCBCDecrypt(kEmptyKey, chromiumCBCIV, payload); altErr == nil {
|
||||
return alt, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func DecryptDPAPI(_ []byte) ([]byte, error) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build linux
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestKEmptyKey_MatchesChromium pins the runtime-derived kEmptyKey to
|
||||
// Chromium's reference bytes in os_crypt_linux.cc.
|
||||
func TestKEmptyKey_MatchesChromium(t *testing.T) {
|
||||
want := []byte{
|
||||
0xd0, 0xd0, 0xec, 0x9c, 0x7d, 0x77, 0xd4, 0x3a,
|
||||
0xc5, 0x41, 0x87, 0xfa, 0x48, 0x18, 0xd1, 0x7f,
|
||||
}
|
||||
assert.Equal(t, want, kEmptyKey)
|
||||
assert.Len(t, kEmptyKey, 16)
|
||||
}
|
||||
|
||||
func TestDecryptChromium_EmptyKeyFallback(t *testing.T) {
|
||||
plaintext := []byte("legacy_kwallet_value")
|
||||
encrypted, err := AESCBCEncrypt(kEmptyKey, chromiumCBCIV, plaintext)
|
||||
require.NoError(t, err)
|
||||
ciphertext := append([]byte("v11"), encrypted...)
|
||||
|
||||
wrongKey := bytes.Repeat([]byte{0xAA}, 16)
|
||||
got, err := DecryptChromium(wrongKey, ciphertext)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, plaintext, got)
|
||||
}
|
||||
|
||||
func TestDecryptChromium_ShortCiphertext(t *testing.T) {
|
||||
key := make([]byte, 16)
|
||||
_, err := DecryptChromium(key, []byte("v11short"))
|
||||
require.ErrorIs(t, err, errShortCiphertext)
|
||||
}
|
||||
@@ -34,6 +34,19 @@ func TestFallbackRetriever(t *testing.T) {
|
||||
assert.Equal(t, key, key2, "fallback key should be the same for any storage")
|
||||
}
|
||||
|
||||
// TestFallbackRetriever_MatchesChromiumKV10Key pins FallbackRetriever's
|
||||
// output to Chromium's kV10Key reference bytes in os_crypt_linux.cc.
|
||||
func TestFallbackRetriever_MatchesChromiumKV10Key(t *testing.T) {
|
||||
want := []byte{
|
||||
0xfd, 0x62, 0x1f, 0xe5, 0xa2, 0xb4, 0x02, 0x53,
|
||||
0x9d, 0xfa, 0x14, 0x7c, 0xa9, 0x27, 0x27, 0x78,
|
||||
}
|
||||
r := &FallbackRetriever{}
|
||||
key, err := r.RetrieveKey("", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, want, key)
|
||||
}
|
||||
|
||||
func TestDefaultRetriever_Linux(t *testing.T) {
|
||||
r := DefaultRetriever()
|
||||
chain, ok := r.(*ChainRetriever)
|
||||
|
||||
+7
-1
@@ -7,6 +7,10 @@ const (
|
||||
// CipherV10 is Chrome 80+ encryption (AES-GCM on Windows, AES-CBC on macOS/Linux).
|
||||
CipherV10 CipherVersion = "v10"
|
||||
|
||||
// CipherV11 is the Linux-only AES-CBC variant where the key comes from
|
||||
// libsecret / kwallet. Same algorithm as CipherV10; only the key source differs.
|
||||
CipherV11 CipherVersion = "v11"
|
||||
|
||||
// CipherV20 is Chrome 127+ App-Bound Encryption.
|
||||
CipherV20 CipherVersion = "v20"
|
||||
|
||||
@@ -26,6 +30,8 @@ func DetectVersion(ciphertext []byte) CipherVersion {
|
||||
switch prefix {
|
||||
case "v10":
|
||||
return CipherV10
|
||||
case "v11":
|
||||
return CipherV11
|
||||
case "v20":
|
||||
return CipherV20
|
||||
default:
|
||||
@@ -37,7 +43,7 @@ func DetectVersion(ciphertext []byte) CipherVersion {
|
||||
// Returns the ciphertext unchanged if no known prefix is found.
|
||||
func stripPrefix(ciphertext []byte) []byte {
|
||||
ver := DetectVersion(ciphertext)
|
||||
if ver == CipherV10 || ver == CipherV20 {
|
||||
if ver == CipherV10 || ver == CipherV11 || ver == CipherV20 {
|
||||
return ciphertext[versionPrefixLen:]
|
||||
}
|
||||
return ciphertext
|
||||
|
||||
@@ -13,6 +13,7 @@ func TestDetectVersion(t *testing.T) {
|
||||
want CipherVersion
|
||||
}{
|
||||
{"v10 prefix", []byte("v10" + "encrypted_data"), CipherV10},
|
||||
{"v11 prefix", []byte("v11" + "encrypted_data"), CipherV11},
|
||||
{"v20 prefix", []byte("v20" + "encrypted_data"), CipherV20},
|
||||
{"no prefix (DPAPI)", []byte{0x01, 0x00, 0x00, 0x00}, CipherDPAPI},
|
||||
{"short input", []byte{0x01, 0x02}, CipherDPAPI},
|
||||
@@ -34,6 +35,7 @@ func Test_stripPrefix(t *testing.T) {
|
||||
want []byte
|
||||
}{
|
||||
{"strips v10", []byte("v10PAYLOAD"), []byte("PAYLOAD")},
|
||||
{"strips v11", []byte("v11PAYLOAD"), []byte("PAYLOAD")},
|
||||
{"strips v20", []byte("v20PAYLOAD"), []byte("PAYLOAD")},
|
||||
{"keeps DPAPI unchanged", []byte{0x01, 0x00, 0x00}, []byte{0x01, 0x00, 0x00}},
|
||||
{"keeps short unchanged", []byte{0x01}, []byte{0x01}},
|
||||
|
||||
Reference in New Issue
Block a user