feat: add note

This commit is contained in:
2025-11-29 13:11:13 +07:00
parent 6a1eb53102
commit c35e2dc9d9
7 changed files with 352 additions and 423 deletions
+2
View File
@@ -1,4 +1,6 @@
Cookies
Local State
cookies.json
+2
View File
@@ -2,4 +2,6 @@
*In 2025, I rewrote this project using Go. The original Python version of this project can be found at `feature/python` branch.*
**It not work anymore. Maybe the convert Go version issue. Check Python version as fallback**
Export Google Chrome cookies in Windows using Go
+19 -2
View File
@@ -1,5 +1,22 @@
module github.com/tiennm99/export-chrome-cookies
go 1.21
go 1.24.0
require github.com/mattn/go-sqlite3 v1.14.17
toolchain go1.24.10
require (
golang.org/x/sys v0.38.0
modernc.org/sqlite v1.40.1
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+49 -2
View File
@@ -1,2 +1,49 @@
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+280 -280
View File
@@ -1,51 +1,292 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"database/sql"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"unsafe"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/sys/windows"
_ "modernc.org/sqlite"
)
// Cookie represents a Chrome cookie in the same format as the Python version
type Cookie struct {
Domain string `json:"domain"`
ExpirationDate int64 `json:"expirationDate"`
HostOnly bool `json:"hostOnly"`
HTTPOnly bool `json:"httpOnly"`
Name string `json:"name"`
Path string `json:"path"`
SameSite string `json:"sameSite"`
Secure bool `json:"secure"`
Session bool `json:"session"`
StoreID string `json:"storeId"`
Value string `json:"value"`
ID int `json:"id"`
Domain string `json:"domain"`
ExpirationDate float64 `json:"expirationDate"`
HostOnly bool `json:"hostOnly"`
HTTPOnly bool `json:"httpOnly"`
Name string `json:"name"`
Path string `json:"path"`
SameSite string `json:"sameSite"`
Secure bool `json:"secure"`
Session bool `json:"session"`
StoreID string `json:"storeId"`
Value string `json:"value"`
ID int `json:"id"`
}
// ConvertToUnixTime converts Windows FILETIME (microseconds since 1601) to Unix epoch time (seconds since 1970)
func ConvertToUnixTime(expiresUTC int64) int64 {
type LocalState struct {
OSCrypt struct {
EncryptedKey string `json:"encrypted_key"`
} `json:"os_crypt"`
}
func main() {
userProfile := os.Getenv("USERPROFILE")
localStatePath := filepath.Join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Local State")
cookiesPath := filepath.Join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Network", "Cookies")
// Get encryption key
key, err := getEncryptionKey(localStatePath)
if err != nil {
fmt.Printf("Error getting encryption key: %v\n", err)
return
}
fmt.Printf("Encryption key length: %d bytes\n", len(key))
fmt.Printf("Key first bytes: %v\n", key[:min(16, len(key))])
// Extract cookies
cookies, err := extractCookies(cookiesPath, key)
if err != nil {
fmt.Printf("Error extracting cookies: %v\n", err)
return
}
// Save to JSON
jsonData, err := json.MarshalIndent(cookies, "", " ")
if err != nil {
fmt.Printf("Error marshaling JSON: %v\n", err)
return
}
err = os.WriteFile("cookies.json", jsonData, 0644)
if err != nil {
fmt.Printf("Error writing JSON file: %v\n", err)
return
}
fmt.Printf("Successfully extracted %d cookies to cookies.json\n", len(cookies))
}
func getEncryptionKey(localStatePath string) ([]byte, error) {
// Always copy a fresh Local State file
workingFile := "Local State"
// Remove old copy if exists
os.Remove(workingFile)
if err := copyFile(localStatePath, workingFile); err != nil {
return nil, fmt.Errorf("failed to copy Local State: %v", err)
}
// Read and parse Local State
data, err := os.ReadFile(workingFile)
if err != nil {
return nil, fmt.Errorf("failed to read Local State: %v", err)
}
var localState LocalState
if err := json.Unmarshal(data, &localState); err != nil {
return nil, fmt.Errorf("failed to parse Local State: %v", err)
}
// Decode base64 key
encryptedKey, err := base64.StdEncoding.DecodeString(localState.OSCrypt.EncryptedKey)
if err != nil {
return nil, fmt.Errorf("failed to decode key: %v", err)
}
fmt.Printf("Encrypted key length: %d bytes\n", len(encryptedKey))
fmt.Printf("Encrypted key prefix: %s\n", string(encryptedKey[:5]))
// Remove "DPAPI" prefix (first 5 bytes)
if len(encryptedKey) < 5 || string(encryptedKey[:5]) != "DPAPI" {
return nil, fmt.Errorf("encrypted key doesn't have DPAPI prefix")
}
encryptedKey = encryptedKey[5:]
// Decrypt using DPAPI
key, err := dpAPIDecrypt(encryptedKey)
if err != nil {
return nil, fmt.Errorf("failed to decrypt key: %v", err)
}
return key, nil
}
func dpAPIDecrypt(data []byte) ([]byte, error) {
dataBlob := windows.DataBlob{
Size: uint32(len(data)),
Data: &data[0],
}
var outBlob windows.DataBlob
err := windows.CryptUnprotectData(&dataBlob, nil, nil, 0, nil, 0, &outBlob)
if err != nil {
return nil, fmt.Errorf("DPAPI decrypt failed: %v", err)
}
decrypted := make([]byte, outBlob.Size)
copy(decrypted, unsafe.Slice(outBlob.Data, outBlob.Size))
windows.LocalFree(windows.Handle(unsafe.Pointer(outBlob.Data)))
return decrypted, nil
}
func extractCookies(cookiesPath string, key []byte) ([]Cookie, error) {
// Always copy a fresh cookies database
workingDB := "Cookies"
// Remove old copy if exists
os.Remove(workingDB)
if err := copyFile(cookiesPath, workingDB); err != nil {
return nil, fmt.Errorf("failed to copy Cookies database: %v", err)
}
// Open database
db, err := sql.Open("sqlite", workingDB)
if err != nil {
return nil, fmt.Errorf("failed to open database: %v", err)
}
defer db.Close()
// Query cookies
query := `SELECT host_key, name, value, encrypted_value, path, expires_utc, is_secure,
is_httponly, has_expires, is_persistent, samesite FROM cookies`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("failed to query cookies: %v", err)
}
defer rows.Close()
var cookies []Cookie
id := 1
for rows.Next() {
var hostKey, name, value, path string
var encryptedValue []byte
var expiresUTC, isSecure, isHTTPOnly, hasExpires, isPersistent, sameSite int64
err := rows.Scan(&hostKey, &name, &value, &encryptedValue, &path, &expiresUTC,
&isSecure, &isHTTPOnly, &hasExpires, &isPersistent, &sameSite)
if err != nil {
continue
}
// Debug output for first few cookies
if id <= 3 {
fmt.Printf("Cookie #%d: %s\n", id, name)
fmt.Printf(" Value field: '%s' (len=%d)\n", value, len(value))
fmt.Printf(" Encrypted field: len=%d\n", len(encryptedValue))
if len(encryptedValue) > 0 {
fmt.Printf(" First bytes: %v\n", encryptedValue[:min(10, len(encryptedValue))])
}
}
// Decrypt value if needed
decryptedValue := value
if value == "" && len(encryptedValue) > 0 {
decryptedValue = decryptData(encryptedValue, key)
if id <= 3 {
fmt.Printf(" Decrypted: '%s'\n", decryptedValue)
}
}
cookie := Cookie{
Domain: hostKey,
ExpirationDate: convertToUnixTime(expiresUTC),
HostOnly: false,
HTTPOnly: isHTTPOnly == 1,
Name: name,
Path: path,
SameSite: convertSameSite(int(sameSite)),
Secure: isSecure == 1,
Session: isPersistent == 0,
StoreID: "0",
Value: decryptedValue,
ID: id,
}
cookies = append(cookies, cookie)
id++
}
return cookies, nil
}
func decryptData(data []byte, key []byte) string {
// Check if data starts with "v10", "v11", or "v20" prefix (Chrome's encryption marker)
if len(data) > 3 && data[0] == 'v' {
version := string(data[0:3])
// v10, v11, v20 all use AES-GCM with 12-byte nonce
if version == "v10" || version == "v11" || version == "v20" {
if len(data) < 15 {
return ""
}
// Nonce is bytes 3-15 (12 bytes)
nonce := data[3:15]
encryptedData := data[15:]
// Create AES-GCM cipher
block, err := aes.NewCipher(key)
if err != nil {
fmt.Printf(" AES cipher error: %v\n", err)
return ""
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
fmt.Printf(" GCM error: %v\n", err)
return ""
}
// Decrypt
decrypted, err := aesgcm.Open(nil, nonce, encryptedData, nil)
if err != nil {
fmt.Printf(" Decrypt error: %v\n", err)
return ""
}
return string(decrypted)
}
}
// Try DPAPI fallback for older encryption
if len(data) > 0 {
if decrypted, err := dpAPIDecrypt(data); err == nil {
return string(decrypted)
}
}
return ""
}
func convertToUnixTime(expiresUTC int64) float64 {
if expiresUTC == 0 {
return 0
}
// Windows FILETIME: 100-nanosecond intervals since January 1, 1601
// Convert to microseconds and then to Unix time (seconds since 1970)
microseconds := expiresUTC / 10
// Chrome's timestamp is microseconds since January 1, 1601
// Convert to Unix timestamp (seconds since January 1, 1970)
chromeEpoch := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
unixEpoch := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
// January 1, 1601 to January 1, 1970 is 11644473600 seconds
return (microseconds / 1000000) - 11644473600
expiresTime := chromeEpoch.Add(time.Duration(expiresUTC) * time.Microsecond)
unixTime := expiresTime.Sub(unixEpoch).Seconds()
return unixTime
}
// ConvertSameSite converts SQLite SameSite value to string representation
func ConvertSameSite(sameSite int64) string {
sameSiteMapping := map[int64]string{
func convertSameSite(sameSite int) string {
sameSiteMap := map[int]string{
-1: "unspecified",
0: "no_restriction",
1: "lax",
@@ -53,273 +294,32 @@ func ConvertSameSite(sameSite int64) string {
3: "none",
}
result, exists := sameSiteMapping[sameSite]
if !exists {
return "unspecified"
if val, ok := sameSiteMap[sameSite]; ok {
return val
}
return result
return "unspecified"
}
// GetEncryptionKey extracts and decrypts the encryption key from Chrome's Local State file
func GetEncryptionKey() ([]byte, error) {
userProfile := os.Getenv("USERPROFILE")
if userProfile == "" {
return nil, fmt.Errorf("USERPROFILE environment variable not found")
func min(a, b int) int {
if a < b {
return a
}
localStatePath := filepath.Join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Local State")
workingLocalState := "Local State"
if _, err := os.Stat(workingLocalState); os.IsNotExist(err) {
if err := CopyFile(localStatePath, workingLocalState); err != nil {
return nil, fmt.Errorf("failed to copy Local State: %v", err)
}
}
data, err := ioutil.ReadFile(workingLocalState)
if err != nil {
return nil, fmt.Errorf("failed to read Local State: %v", err)
}
var localState struct {
OSCrypt struct {
EncryptedKey string `json:"encrypted_key"`
} `json:"os_crypt"`
}
if err := json.Unmarshal(data, &localState); err != nil {
return nil, fmt.Errorf("failed to parse Local State: %v", err)
}
// Decode the encryption key from Base64
encryptedKey, err := base64.StdEncoding.DecodeString(localState.OSCrypt.EncryptedKey)
if err != nil {
return nil, fmt.Errorf("failed to decode encrypted key: %v", err)
}
// Remove 'DPAPI' (5 bytes prefix)
if len(encryptedKey) < 5 {
return nil, fmt.Errorf("invalid encrypted key length")
}
encryptedKey = encryptedKey[5:]
// DPAPI decryption (Windows only)
return decryptDataDPAPI(encryptedKey)
return b
}
// DecryptDataDPAPI uses Windows DPAPI to decrypt data
func decryptDataDPAPI(encryptedData []byte) ([]byte, error) {
// This requires Windows-specific DPAPI calls
// We'll use a simplified approach for now and note that this needs Windows API
// For demonstration, we'll return a placeholder
// In a real implementation, you'd use golang.org/x/sys/windows to call DPAPI functions
return nil, fmt.Errorf("DPAPI decryption requires Windows-specific implementation")
}
// DecryptData attempts to decrypt using AES-GCM first, then falls back to DPAPI
func DecryptData(encryptedValue []byte, encryptionKey []byte) string {
if len(encryptedValue) == 0 {
return ""
}
// Try AES-GCM decryption first
decrypted, err := decryptDataAESGCM(encryptedValue, encryptionKey)
if err == nil {
return decrypted
}
// Try DPAPI decryption
decryptedBytes, err := decryptDataDPAPI(encryptedValue)
if err == nil {
return string(decryptedBytes)
}
return ""
}
// DecryptDataAESGCM decrypts data using AES-GCM
func decryptDataAESGCM(encryptedValue []byte, key []byte) (string, error) {
if len(encryptedValue) < 15 {
return "", fmt.Errorf("encrypted data too short")
}
// Chrome stores AES-GCM encrypted data with prefix: 0x010000 + nonce + ciphertext + tag
// Extract nonce (12 bytes) from position 3 to 15
_ = encryptedValue[3:15] // nonce - used in full implementation
_ = encryptedValue[15:] // ciphertextWithTag - used in full implementation
// For AES-GCM, we need to implement the decryption
// This is a simplified version - full implementation would use crypto/aes and crypto/cipher
return "", fmt.Errorf("AES-GCM decryption requires full implementation")
}
func main() {
userProfile := os.Getenv("USERPROFILE")
if userProfile == "" {
fmt.Println("Error: USERPROFILE environment variable not found")
return
}
cookiesPath := filepath.Join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Network", "Cookies")
workingCookies := "Cookies"
// Copy the cookies file to avoid database locking
if _, err := os.Stat(workingCookies); os.IsNotExist(err) {
if err := CopyFile(cookiesPath, workingCookies); err != nil {
fmt.Printf("Error: Failed to copy Cookies file: %v\n", err)
return
}
}
// Open the SQLite database
db, err := sql.Open("sqlite3", workingCookies)
if err != nil {
fmt.Printf("Error: Failed to open database: %v\n", err)
return
}
defer db.Close()
// Query cookies from the database
rows, err := db.Query(`
SELECT host_key, name, value, encrypted_value, path, expires_utc, is_secure, is_httponly, has_expires,
is_persistent, samesite FROM cookies
`)
if err != nil {
fmt.Printf("Error: Failed to query database: %v\n", err)
return
}
defer rows.Close()
encryptionKey, err := GetEncryptionKey()
if err != nil {
fmt.Printf("Warning: Failed to get encryption key: %v\n", err)
// Continue with empty key for already decrypted cookies
encryptionKey = nil
}
var cookiesList []Cookie
var cookieID int = 1
for rows.Next() {
var hostKey, name, value, encryptedValue, path sql.NullString
var expiresUTC, samesite sql.NullInt64
var isSecure, isHTTPOnly, hasExpires, isPersistent sql.NullBool
err := rows.Scan(
&hostKey, &name, &value, &encryptedValue, &path, &expiresUTC, &isSecure, &isHTTPOnly, &hasExpires,
&isPersistent, &samesite,
)
if err != nil {
fmt.Printf("Warning: Failed to scan row: %v\n", err)
continue
}
var decryptedValue string
if value.Valid && value.String != "" {
decryptedValue = value.String
} else if encryptedValue.Valid && encryptedValue.String != "" {
encryptedBytes := []byte(encryptedValue.String)
decryptedValue = DecryptData(encryptedBytes, encryptionKey)
} else {
decryptedValue = ""
}
// Convert types for JSON serialization
domain := ""
if hostKey.Valid {
domain = hostKey.String
}
cookieName := ""
if name.Valid {
cookieName = name.String
}
cookiePath := "/"
if path.Valid {
cookiePath = path.String
}
expirationDate := int64(0)
if expiresUTC.Valid {
expirationDate = ConvertToUnixTime(expiresUTC.Int64)
}
sameSite := "unspecified"
if samesite.Valid {
sameSite = ConvertSameSite(samesite.Int64)
}
httpOnly := false
if isHTTPOnly.Valid {
httpOnly = isHTTPOnly.Bool
}
secure := false
if isSecure.Valid {
secure = isSecure.Bool
}
session := true
if isPersistent.Valid {
session = !isPersistent.Bool
}
cookie := Cookie{
Domain: domain,
ExpirationDate: expirationDate,
HostOnly: false,
HTTPOnly: httpOnly,
Name: cookieName,
Path: cookiePath,
SameSite: sameSite,
Secure: secure,
Session: session,
StoreID: "0",
Value: decryptedValue,
ID: cookieID,
}
cookiesList = append(cookiesList, cookie)
cookieID++
}
if err := rows.Err(); err != nil {
fmt.Printf("Error: Failed to iterate rows: %v\n", err)
return
}
// Write cookies to JSON file
jsonData, err := json.MarshalIndent(cookiesList, "", " ")
if err != nil {
fmt.Printf("Error: Failed to marshal JSON: %v\n", err)
return
}
if err := ioutil.WriteFile("cookies.json", jsonData, 0644); err != nil {
fmt.Printf("Error: Failed to write cookies.json: %v\n", err)
return
}
fmt.Printf("Successfully exported %d cookies to cookies.json\n", len(cookiesList))
}
// CopyFile copies a file from src to dst
func CopyFile(src, dst string) error {
source, err := os.Open(src)
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
defer sourceFile.Close()
destination, err := os.Create(dst)
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
defer destFile.Close()
_, err = io.Copy(destination, source)
_, err = io.Copy(destFile, sourceFile)
return err
}
}
-136
View File
@@ -1,136 +0,0 @@
import base64
import json
import os
import shutil
import sqlite3
from datetime import datetime, timedelta
import win32crypt # pip install pypiwin32
from Crypto.Cipher import AES # pip install pycryptodome
# This is Windows version
# You may want to edit these values to match your OS
local_state_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data",
"Local State")
cookies_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "Default",
"Network", "Cookies")
def convert_to_unix_time(expires_utc):
"""
:param expires_utc: microseconds diff since January, 1601
:return: epoch time in seconds
"""
if expires_utc == 0:
return 0
# Define the start date as January 1, 1601
start_date = datetime(1601, 1, 1)
# Calculate the timedelta from the start date to the expires_utc date
expires_date = start_date + timedelta(microseconds=expires_utc)
# Define the Unix epoch start date as January 1, 1970
epoch_start_date = datetime(1970, 1, 1)
# Calculate the timedelta from the Unix epoch start date to the expires_utc date
unix_time = (expires_date - epoch_start_date).total_seconds()
return unix_time
def convert_samesite(samesite):
samesite_mapping = {
-1: "unspecified",
0: "no_restriction",
1: "lax",
2: "strict",
3: "none",
}
return samesite_mapping.get(samesite, "unspecified")
def get_encryption_key():
working_local_state = "Local State"
if not os.path.isfile(working_local_state):
shutil.copyfile(local_state_path, working_local_state)
with open(working_local_state, "r", encoding="utf-8") as f:
local_state = f.read()
local_state = json.loads(local_state)
# decode the encryption key from Base64
key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
# remove 'DPAPI' str
key = key[5:]
# return decrypted key that was originally encrypted
# using a session key derived from current user's logon credentials
# doc: http://timgolden.me.uk/pywin32-docs/win32crypt.html
return win32crypt.CryptUnprotectData(key, None, None, None, 0)[1]
def decrypt_data(data, key):
try:
# get the initialization vector
iv = data[3:15]
data = data[15:]
# generate cipher
cipher = AES.new(key, AES.MODE_GCM, iv)
# decrypt password
return cipher.decrypt(data)[:-16].decode()
except:
try:
return str(win32crypt.CryptUnprotectData(data, None, None, None, 0)[1])
except:
# not supported
return ""
def main():
# local sqlite Chrome cookie database path
# copy the file to current directory
# as the database will be locked if chrome is currently open
working_cookies = "Cookies"
if not os.path.isfile(working_cookies):
shutil.copyfile(cookies_path, working_cookies)
# connect to the database
db = sqlite3.connect(working_cookies)
# ignore decoding errors
db.text_factory = lambda b: b.decode(errors="ignore")
cursor = db.cursor()
# get the cookies from `cookies` table
cursor.execute("""
SELECT host_key, name, value, encrypted_value, path, expires_utc, is_secure, is_httponly, has_expires,
is_persistent, samesite FROM cookies""")
encryption_key = get_encryption_key()
cookies_list = []
for i, (host_key, name, value, encrypted_value, path, expires_utc, is_secure, is_httponly, has_expires,
is_persistent, samesite) in enumerate(cursor.fetchall()):
if not value:
decrypted_value = decrypt_data(encrypted_value, encryption_key)
else:
# already decrypted
decrypted_value = value
cookies_list.append({
"domain": host_key,
"expirationDate": convert_to_unix_time(expires_utc),
"hostOnly": False, # This information is not available in the SQLite database
"httpOnly": bool(is_httponly),
"name": name,
"path": path,
"sameSite": convert_samesite(samesite),
"secure": bool(is_secure),
"session": not bool(is_persistent),
"storeId": "0", # This information is not available in the SQLite database
"value": decrypted_value,
"id": i + 1
})
# commit changes
db.commit()
# close connection
db.close()
with open('cookies.json', 'w') as f:
json.dump(cookies_list, f, indent=4)
if __name__ == "__main__":
main()
-3
View File
@@ -1,3 +0,0 @@
pycryptodome==3.20.0
pypiwin32==223
pywin32==306