Skip to content

Commit

Permalink
Compatible with v80 Chrome cookie encryption in Windows / Windows tim…
Browse files Browse the repository at this point in the history
…estamp tweak (#48)

Updated .gitignore

Update setup.py

Fixed key issue
  • Loading branch information
brooksmcmillin committed Feb 17, 2020
1 parent 6e8247d commit 1c25baf
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
**/*.swp
__pycache__
*.pyc
61 changes: 54 additions & 7 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import lz4.block
import datetime
import configparser
import base64
from Crypto.Cipher import AES

try:
import json
Expand Down Expand Up @@ -67,7 +69,7 @@ def windows_group_policy_path():

# Code adapted slightly from https://github.com/Arnie97/chrome-cookies
def crypt_unprotect_data(
cipher_text=b'', entropy=b'', reserved=None, prompt_struct=None
cipher_text=b'', entropy=b'', reserved=None, prompt_struct=None, is_key=False
):
# we know that we're running under windows at this point so it's safe to try these imports
import ctypes
Expand Down Expand Up @@ -97,7 +99,10 @@ class DataBlob(ctypes.Structure):
buffer_out = ctypes.create_string_buffer(int(blob_out.cbData))
ctypes.memmove(buffer_out, blob_out.pbData, blob_out.cbData)
map(ctypes.windll.kernel32.LocalFree, [desc, blob_out.pbData])
return description, buffer_out.value
if is_key:
return description, buffer_out.raw
else:
return description, buffer_out.value


class Chrome:
Expand Down Expand Up @@ -125,6 +130,25 @@ def __init__(self, cookie_file=None, domain_name=""):
or os.path.expanduser('~/.config/chromium/Default/Cookies') \
or os.path.expanduser('~/.config/google-chrome-beta/Default/Cookies')
elif sys.platform == "win32":

# Read key from file
key_file = glob.glob(os.path.join(os.getenv('APPDATA', ''), '..\Local\\Google\\Chrome\\User Data\\Local State')) \
or glob.glob(os.path.join(os.getenv('LOCALAPPDATA', ''), 'Google\\Chrome\\User Data\\Local State')) \
or glob.glob(os.path.join(os.getenv('APPDATA', ''), 'Google\\Chrome\\User Data\\Local State'))

if isinstance(key_file, list):
if key_file:
key_file = key_file[0]

if key_file:
f = open(key_file, 'rb')
key_file_json = json.load(f)
key64 = key_file_json['os_crypt']['encrypted_key'].encode('utf-8')

# Decode Key, get rid of DPAPI prefix, unprotect data
keydpapi = base64.standard_b64decode(key64)[5:]
_, self.key = crypt_unprotect_data(keydpapi, is_key=True)

# get cookie file from APPDATA
# Note: in windows the \\ is required before a u to stop unicode errors
cookie_file = cookie_file or windows_group_policy_path() \
Expand Down Expand Up @@ -170,10 +194,18 @@ def load(self):
host, path, secure, expires, name = item[:5]
if item[3] != 0:
# ensure dates don't exceed the datetime limit of year 10000
offset = min(int(item[3]), 265000000000000000)
delta = datetime.timedelta(microseconds=offset)
expires = epoch_start + delta
expires = expires.timestamp()
try:
offset = min(int(item[3]), 265000000000000000)
delta = datetime.timedelta(microseconds=offset)
expires = epoch_start + delta
expires = expires.timestamp()
# Windows 7 has a further constraint
except OSError:
offset = min(int(item[3]), 32536799999000000)
delta = datetime.timedelta(microseconds=offset)
expires = epoch_start + delta
expires = expires.timestamp()

value = self._decrypt(item[5], item[6])
c = create_cookie(host, path, secure, expires, name, value)
cj.set_cookie(c)
Expand All @@ -198,7 +230,22 @@ def _decrypt(self, value, encrypted_value):
"""

if sys.platform == 'win32':
return self._decrypt_windows_chrome(value, encrypted_value)
try:
return self._decrypt_windows_chrome(value, encrypted_value)

# Fix for change in Chrome 80
except RuntimeError: # Failed to decrypt the cipher text with DPAPI
if not self.key:
raise RuntimeError('Failed to decrypt the cipher text with DPAPI and no AES key.')
# Encrypted cookies should be prefixed with 'v10' according to the
# Chromium code. Strip it off.
encrypted_value = encrypted_value[3:]
nonce, tag = encrypted_value[:12], encrypted_value[-16:]
aes = AES.new(self.key, AES.MODE_GCM, nonce=nonce)


data = aes.decrypt_and_verify(encrypted_value[12:-16], tag)
return data.decode()

if value or (encrypted_value[:3] != b'v10'):
return value
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
author_email='boris.ivan.babic@gmail.com',
description='Loads cookies from your browser into a cookiejar object so can download with urllib and other libraries the same content you see in the web browser.',
url='https://github.com/borisbabic/browser_cookie3',
install_requires=['pyaes','pbkdf2','keyring','lz4'],
install_requires=['pyaes','pbkdf2','keyring','lz4', 'pycryptodome'],
license='lgpl'
)

0 comments on commit 1c25baf

Please sign in to comment.