解密并重新加密Chrome cookie

问题描述 投票:0回答:1

我正在尝试解密Chrome的cookie SQLite DB,并将解密的cookie移动到另一台计算机(浏览器),并重新加密数据库,并复制会话。

这是我的计划:

  1. 使用 DPAPI
    Local State
     中的 
    C:\Users\[username]\AppData\Local\Google\Chrome\User Data\Local State
  2. 解密 AES 密钥
  3. 使用解密密钥解密
    C:\Users\[username]\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies
  4. 中的 Cookie DB
  5. 将解密后的Cookie DB复制到另一台计算机上
  6. 生成随机 AES 密钥/随机数并对另一台计算机上传输的明文 Cookie DB 进行加密。替换另一台计算机上的原始 Cookies DB。
  7. 使用 DPAPI 加密 AES 密钥并替换另一台计算机上
    Local State
    中的相关条目。

我有以下 2 个 Python 文件来执行上述操作:

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'\x76\x31\x30' +\
            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'\x76\x31\x30' +\
            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)}")

通过这些函数,我可以获取明文cookie并验证,如果我在Chrome中手动复制cookie文本,我可以获取目标会话。

if cookie_name == "BITBUCKETSESSIONID":
            print(f"Decrypted cookie (bitbucket): {decrypted_value.decode()}")

解密和加密来回工作也没有问题。

但是,如果我替换修改后的

Cookies
文件和
Local State
文件,Chrome 将不会读取迁移的 cookie。

我可以知道这里出了什么问题吗?


根据 Topaco 在评论中的建议,我通过以下方式修改了我的函数:

  1. 使用另一台计算机上现有的本地 AES 密钥
  2. 生成新的随机数 (
    nonce = os.urandom(12)
    )
  3. encrypt
    更改为
    encrypt_and_digest
    ,将
    decrypt
    更改为
    decrypt_and_verify
  4. encrypt_and_digest
    返回的新验证标签和随机数存储在
    encrypted_cookie

...以下是新功能:

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
from os.path import expandvars

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
        
        nonce = os.urandom(12)

        aes_cipher = new(key=key, mode=MODE_GCM, nonce=nonce)
        # encrypted_value = aes_cipher.encrypt(decrypted_value[15: -16]) # wrong
        encrypted_value, verification_tag = aes_cipher.encrypt_and_digest(decrypted_value[15: -16])
        # print(f"Encrypted cookie:\n  {decrypt.bytes_to_hex(encrypted_value)}\n  {encrypted_value}")

        # verification_tag = decrypted_value[-16:] # wrong
        # print(f"Verification tag:\n  {decrypt.bytes_to_hex(verification_tag)}\n  {verification_tag}")

        # nonce = decrypted_value[3:15] # wrong
        # print(f"Nonce:\n  {decrypt.bytes_to_hex(nonce)}\n  {nonce}")

        encrypted_cookie = b'\x76\x31\x30' +\
            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()

def get_local_state_key():
    local_state = json.load(open(expandvars('%LOCALAPPDATA%/Google/Chrome/User Data/Local State')))
    encrypted_key = local_state['os_crypt']['encrypted_key']
    decrypted_key = decrypt.decrypt_dpapi_blob(encrypted_key)[1]
    return decrypted_key

# Example usage
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)
    # Using existing key
    key = get_local_state_key()
    encrypt_cookies(cookies_db, key)
    # encrypted_key = encrypt_dpapi_blob(key)
    # print(f"Encrypted key:\n  {str(encrypted_key, 'utf-8')}")

    # wrong
    # 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
import encrypt

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]) # wrong
        decrypted_value = aes_cipher.decrypt_and_verify(encrypted_value[15: -16], encrypted_value[-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'\x76\x31\x30' +\
            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()

# Custom function to display all bytes in the \x[something] format
def bytes_to_hex(byte_data):
    return f"b'{''.join(f'\\x{byte:02x}' for byte in byte_data)}'"

# Example usage
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 = encrypt.get_local_state_key()
        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)}")
python google-chrome encryption cookies dpapi
1个回答
0
投票

根据Topaco在评论中的建议,并根据我自己的尝试,我通过以下方式修改了我的功能:

  1. 使用另一台计算机上现有的本地 AES 密钥
  2. 生成新的随机数(nonce = os.urandom(12))
  3. 将加密更改为 encrypt_and_digest,并将解密更改为解密_and_verify
  4. 将 encrypt_and_digest & nonce 返回的新验证标签存储在 crypto_cookie 中
  5. 在 SQL 查询中用单引号替换双引号 ...以下是新功能:

encrypt.py

import argparse
import json
import os
import base64
import sqlite3
from os.path import expandvars

from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
from win32.win32crypt import CryptProtectData # pip install pywin32

import decrypt


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
        if decrypted_value is None or len(decrypted_value) == 0:
            continue
        
        nonce = os.urandom(12)
        aes_cipher = new(key=key, mode=MODE_GCM, nonce=nonce)
        encrypted_value, verification_tag = aes_cipher.encrypt_and_digest(decrypted_value)

        encrypted_cookie = b'\x76\x31\x30' +\
            nonce +\
            encrypted_value +\
            verification_tag

        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = '{cookie_name}'"
        params = [encrypted_cookie]
        cursor.execute(query, params)

    conn.commit()
    conn.close()


def get_local_state_key():
    local_state = json.load(open(expandvars('%LOCALAPPDATA%/Google/Chrome/User Data/Local State')))
    encrypted_key = local_state['os_crypt']['encrypted_key']
    decrypted_key = decrypt.decrypt_dpapi_blob(encrypted_key)[1]
    return decrypted_key


if __name__ == "__main__":
    # Arg: cookies_db
    parser = argparse.ArgumentParser()
    parser.add_argument("--cookies", help="Name of the cookies database file", default="Cookies")
    args = parser.parse_args()

    cookies_db = os.path.join(os.getcwd(), args.cookies)
    key = get_local_state_key()
    encrypt_cookies(cookies_db, key)

decrypt.py

import argparse
import base64
import os
import sqlite3
import sys

from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
from win32.win32crypt import CryptUnprotectData # pip install pywin32

import encrypt


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
        if encrypted_value is None or len(encrypted_value) == 0:
            continue

        verification_tag = encrypted_value[-16:]
        aes_cipher = new(key=key, mode=MODE_GCM, nonce=encrypted_value[3:15])
        decrypted_value = aes_cipher.decrypt_and_verify(ciphertext=encrypted_value[15: -16], received_mac_tag=verification_tag)
        # if cookie_name == "BITBUCKETSESSIONID":
        #     print(f"Decrypted cookie (bitbucket):\n  {decrypted_value.decode()}")
        #     print(f"Verification tag (bitbucket):\n  {bytes_to_hex(verification_tag)}")
        #     print(f"Nonce (bitbucket):\n  {bytes_to_hex(nonce)}")
        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = '{cookie_name}'"
        params = [decrypted_value]
        cursor.execute(query, params)

    conn.commit()
    conn.close()


if __name__ == "__main__":
    try:
        decrypted_key = encrypt.get_local_state_key()
    except Exception as e:
        print("Decryption failed:", str(e))
        sys.exit(1)
    
    # Arg: cookies_db
    parser = argparse.ArgumentParser()
    parser.add_argument("--cookies", help="Name of the cookies database file", default="Cookies")
    args = parser.parse_args()
    cookies_db = os.path.join(os.getcwd(), args.cookies)

    decrypt_cookies(cookies_db, decrypted_key)

现在这些功能可用于在计算机之间迁移 cookie,不会出现错误。

© www.soinside.com 2019 - 2024. All rights reserved.