Revert "Merge branch 'feature/export-chrome-cookies-go' into main"

Keeps the branch in history only; main's content is unchanged.
This commit is contained in:
tiennm99 committed 2026-09-30 20:04:13 +07:00
1 parent 0693cb3724
commit fbc08a2435
7 files changed
+329 -422

No files matched your search

+151 -23
View File
@@ -1,38 +1,166 @@
.idea
Cookies
Local State
cookies.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
# C extensions
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Code coverage profiles and other test artifacts
*.out
coverage.*
*.coverprofile
profile.cov
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Dependency directories (remove the comment below to include it)
# vendor/
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Go workspace file
go.work
go.work.sum
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# env file
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Editor/IDE
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# .vscode/
+39 -3
View File
@@ -1,7 +1,43 @@
# export-chrome-cookies
*In 2025, I rewrote this project using Go. The original Python version of this project can be found at `feature/python` branch.*
Python script to export Google Chrome cookies on Windows.
**This method not work anymore, even the Python version**
## Quick start
Export Google Chrome cookies in Windows using Go
```bash
pip install -r requirements.txt
python main.py
```
> **Note:** Run on Windows with Chrome fully closed. Chrome locks its SQLite cookie database while running.
## Output format
Cookies are written to `cookies.json` in the current directory. Each entry follows the WebExtension cookie schema:
```json
{
"domain": ".example.com",
"name": "session_id",
"value": "abc123",
"path": "/",
"secure": true,
"httpOnly": false,
"sameSite": "lax",
"expirationDate": 1735689600,
"session": false
}
```
## Chrome v10+ encryption
Chrome 80+ on Windows encrypts cookie values using AES-256-GCM with a key stored in `Local State` (protected by DPAPI). The script:
1. Reads the base64-encoded encrypted key from `AppData\Local\Google\Chrome\User Data\Local State`.
2. Decrypts it with `win32crypt.CryptUnprotectData`.
3. Uses the key to decrypt each cookie value from the `Cookies` SQLite database.
You must run the script **as the same Windows user** who owns the Chrome profile — DPAPI keys are user-scoped.
## License
Apache-2.0 — see [LICENSE](LICENSE).
-22
View File
@@ -1,22 +0,0 @@
module github.com/tiennm99/export-chrome-cookies
go 1.24.0
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
View File
@@ -1,49 +0,0 @@
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=
-325
View File
@@ -1,325 +0,0 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"database/sql"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"
"unsafe"
"golang.org/x/sys/windows"
_ "modernc.org/sqlite"
)
type Cookie struct {
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"`
}
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
}
// 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)
expiresTime := chromeEpoch.Add(time.Duration(expiresUTC) * time.Microsecond)
unixTime := expiresTime.Sub(unixEpoch).Seconds()
return unixTime
}
func convertSameSite(sameSite int) string {
sameSiteMap := map[int]string{
-1: "unspecified",
0: "no_restriction",
1: "lax",
2: "strict",
3: "none",
}
if val, ok := sameSiteMap[sameSite]; ok {
return val
}
return "unspecified"
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
return err
}
+136
View File
@@ -0,0 +1,136 @@
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
@@ -0,0 +1,3 @@
pycryptodome==3.20.0
pypiwin32==223
pywin32==306