I’m trying to decrypt Chrome’s cookie SQLite DB, and move the decrypted cookies to another computer (browser), and re-encrypt the DB, and replicate sessions.
Here is what I plan:
- Decrypt AES key from
Local State
inC:Users[username]AppDataLocalGoogleChromeUser DataLocal State
using DPAPI - Use decrypted key to decrypt Cookie DB in
C:Users[username]AppDataLocalGoogleChromeUser DataDefaultNetworkCookies
- Copy the decrypted Cookie DB to another computer
- Generate random AES key/nonce and encrypt the plaintext Cookie DB transferred on the other computer. Substitute original Cookies DB on the other computer.
- Encrypt AES key using DPAPI and substitute associated entry in
Local State
on the other computer.
And I have the following 2 Python files to do things described above:
encrypt.py
:
from win32.win32crypt import CryptProtectData
import base64
import sqlite3
import os
from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
import decrypt
import json
def encrypt_dpapi_blob(decrypted_blob):
encrypted_blob = CryptProtectData(decrypted_blob, DataDescr="Google Chrome", OptionalEntropy=None, Reserved=None, PromptStruct=None, Flags=0)
encrypted_blob = b'DPAPI' + encrypted_blob
encrypted_blob_base64 = base64.b64encode(encrypted_blob)
return encrypted_blob_base64
def encrypt_cookies(cookies_db, key):
sqlite3.enable_callback_tracebacks(True)
conn = sqlite3.connect(cookies_db)
query = "SELECT name, encrypted_value FROM cookies"
cursor = conn.execute(query)
query_res = cursor.fetchall()
for row in query_res:
cookie_name, decrypted_value = row
# print(f"Encrypting cookie: {cookie_name}")
if decrypted_value is None or len(decrypted_value) == 0:
# print("No decrypted value found.")
continue
aes_cipher = new(key=key, mode=MODE_GCM, nonce=decrypted_value[3:15])
encrypted_value = aes_cipher.encrypt(decrypted_value[15: -16])
# print(f"Encrypted cookie:n {decrypt.bytes_to_hex(encrypted_value)}n {encrypted_value}")
verification_tag = decrypted_value[-16:]
# print(f"Verification tag:n {decrypt.bytes_to_hex(verification_tag)}n {verification_tag}")
nonce = decrypted_value[3:15]
# print(f"Nonce:n {decrypt.bytes_to_hex(nonce)}n {nonce}")
encrypted_cookie = b'x76x31x30' +
nonce +
encrypted_value +
verification_tag
query = f"UPDATE cookies SET encrypted_value = ? WHERE name = "{cookie_name}""
params = [encrypted_cookie]
cursor.execute(query, params)
# print("")
conn.commit()
conn.close()
if __name__ == "__main__":
cookies_db = os.path.join(os.getcwd(), "Cookies")
# print(f"Decrypted key:n {decrypt.bytes_to_hex(key)}n {key}")
key = os.urandom(32)
encrypt_cookies(cookies_db, key)
encrypted_key = encrypt_dpapi_blob(key)
print(f"Encrypted key:n {str(encrypted_key, 'utf-8')}")
local_state = json.load(open('Local State'))
local_state['os_crypt']['encrypted_key'] = encrypted_key.decode()
json.dump(local_state, open('Local State', 'w'))
decrypt.py
:
from win32.win32crypt import CryptUnprotectData
import base64
import sqlite3
import os
from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
import sys
import json
def decrypt_dpapi_blob(encrypted_blob):
encrypted_blob = base64.b64decode(encrypted_blob)[5:] # Leading bytes "DPAPI" need to be removed
decrypt_res = CryptUnprotectData(encrypted_blob, None, None, None, 0)
return decrypt_res
def decrypt_cookies(cookies_db, key):
sqlite3.enable_callback_tracebacks(True)
conn = sqlite3.connect(cookies_db)
query = "SELECT name, encrypted_value FROM cookies"
cursor = conn.execute(query)
query_res = cursor.fetchall()
for row in query_res:
cookie_name, encrypted_value = row
# print(f"Decrypting cookie: {cookie_name}")
if encrypted_value is None or len(encrypted_value) == 0:
# print("No encrypted value found.")
continue
aes_cipher = new(key=key, mode=MODE_GCM, nonce=encrypted_value[3:15])
decrypted_value = aes_cipher.decrypt(encrypted_value[15: -16])
# print(f"Decrypted cookie:n {bytes_to_hex(decrypted_value)}n {decrypted_value}")
if cookie_name == "BITBUCKETSESSIONID":
print(f"Decrypted cookie (bitbucket): {decrypted_value.decode()}")
verification_tag = encrypted_value[-16:]
# print(f"Verification tag:n {bytes_to_hex(verification_tag)}n {verification_tag}")
nonce = encrypted_value[3:15]
# print(f"Nonce:n {bytes_to_hex(nonce)}n {nonce}")
decrypted_cookie = b'x76x31x30' +
nonce +
decrypted_value +
verification_tag
query = f"UPDATE cookies SET encrypted_value = ? WHERE name = "{cookie_name}""
params = [decrypted_cookie]
cursor.execute(query, params)
# print("")
conn.commit()
conn.close()
def bytes_to_hex(byte_data):
return f"b'{''.join(f'\x{byte:02x}' for byte in byte_data)}'"
if __name__ == "__main__":
encrypted_key_base64 = json.load(open('Local State'))['os_crypt']['encrypted_key']
# print(f"Encrypted key:n {encrypted_key_base64}")
try:
decrypted_key = decrypt_dpapi_blob(encrypted_key_base64)[1]
print(f"Decrypted key:n {bytes_to_hex(decrypted_key)}")
except Exception as e:
print("Decryption failed:", str(e))
sys.exit(1)
# get current working directory path
cookies_db = os.path.join(os.getcwd(), "Cookies")
decrypt_cookies(cookies_db, decrypted_key)
# print(f"Decrypted key:n {bytes_to_hex(decrypted_key)}")
With these functions, I can get the plaintext cookie and verified that, if I manually copy the cookie text in Chrome, I can get the target session.
if cookie_name == "BITBUCKETSESSIONID":
print(f"Decrypted cookie (bitbucket): {decrypted_value.decode()}")
However, if I substitute the modified Cookies
file and Local State
file, Chrome will not read the migrated cookie.
May I know what is wrong here?