Chrome cookies cookie decryption write (version 80+)

1. Historical changes

Before 80, decryption: win32crypt.CryptUnprotectData(encrypted_value_bytes, None, None, None, 0) [1], there are many old texts, search by yourself.

80, changed: https://github.com/chromium/chromium/blob/master/components/os_crypt/os_crypt_win.cc

AESGCM encrypted cookie plaintext

def EncryptString(key,plaintext):
    plainbytes=plaintext.encode('utf-8')
    nonce=os.urandom(12)
    aesgcm=AESGCM(key)
    cipherbytes=aesgcm.encrypt(nonce,plainbytes,None)
    data=b'v10'+nonce+cipherbytes
    #也有v11的,反正就前3字节,无影响
    return data

The key of AESGCM is generated like this: (1) random 32-byte DPAPI encryption (2) 5-byte b'DPAPI' header (3) base64 encoding

def generate_a_new_key():
    key=os.urandom(32)
    encrypted_key=win32crypt.CryptProtectData(key,None,None,None,None,0)
    encrypted_key_with_header=b'DPAPI'+encrypted_key
    base64_encrypted_key=base64.b64encode(encrypted_key_with_header)
    return base64_encrypted_key

Second, read and decrypt

Therefore, decryption is: (1) read the ciphertext (bytes) in the database (2) AESGCM decryption. Two paths:

Cookies file (sqlite3 db, storing website cookies)
insert image description here
Local State file (json, storing various types, key is also in it) insert image description here
Python reads and decrypts

# -*- coding=utf-8 -*-
import os
import json
import base64
import sqlite3
import win32crypt
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

#读取chrome保存在json文件中的key(str)
def GetString(LocalState):
    with open(LocalState,'r',encoding='utf-8') as f:
        s=json.load(f)['os_crypt']['encrypted_key']
    return s

#base64解码,DPAPI解密,得到真实的AESGCM key(bytes)
def pull_the_key(base64_encrypted_key):
    encrypted_key_with_header=base64.b64decode(base64_encrypted_key)
    encrypted_key=encrypted_key_with_header[5:]
    key=win32crypt.CryptUnprotectData(encrypted_key,None,None,None,0)[1]
    return key

#AESGCM解密
def DecryptString(key,data):
    nonce,cipherbytes=data[3:15],data[15:]
    aesgcm=AESGCM(key)
    plainbytes=aesgcm.decrypt(nonce,cipherbytes,None)
    plaintext=plainbytes.decode('utf-8')
    return plaintext

if __name__ == '__main__':

    UserDataDir=os.environ['LOCALAPPDATA']+r'\Google\Chrome\User Data'
    LocalStateFilePath=UserDataDir+r'\Local State'
    CookiesFilePath=UserDataDir+r'\Default\Cookies'
    #反正就是上面图片中的两个文件名的路径
    #默认路径可能随着版本有所变化,找一找

    con=sqlite3.connect(CookiesFilePath)
    #con.text_factory = bytes
    res=con.execute('select host_key,name,encrypted_value from cookies').fetchall()
    con.close()
    #此处encrypted_value在sqlite中声明的是BLOB,官方sqlite库应该读进来就是bytes。
    #评论有反应utf-8 decode错误的,显然把自动类型转化成了TEXT,多整了一次decode()
    #最新sqlite dll没这问题,还支持json,,,还在用3.7左右python的建议更新一波
    
    key=pull_the_key(GetString(LocalStateFilePath))
    for i in res:
    	print(i[0],i[1],DecryptString(key,i[2])

(This update is a wave, using the latest version of sqlites3 official website, no problem, 100 million records, test verification,,,)
insert image description here

Third, construct write-back

insert image description here(chrome 93 cookies sqlite db)

As little as possible, it is enough to write 10 required fields. The hype seems to be the three 'time stamps': creation_utc, expires_utc, last_access_utc,

I took a look at Baidu (actually, what Baidu doesn’t reach are all marketing accounts, I know, post it,,, github, stack, google a bit), it seems that this statement is more accurate:

On windows, chrome uses: Windows file time, which is different from the daily unix timestamp:

(1) Starting point. 12 midnight (utc) on January 1, 1601 vs 12 midnight (utc) on January 1, 1970 (2) accuracy. 'subtle' vs 'seconds'

(https://docs.microsoft.com/en-us/dotnet/api/system.datetime.tofiletimeutc?view=net-5.0)

def ToFileTimeUtc(isostr=''):
    from datetime import datetime,timedelta,timezone
    if isostr:
        l=[int(i) for i in isostr.split(' ')[0].split('-')]+[int(i) for i in isostr.split(' ')[-1].split(':')]
        end=datetime(*l,tzinfo=timezone.utc)
    else:
        end=datetime.now(timezone.utc)
    start=datetime(1601,1,1,tzinfo=timezone.utc)
    return int((end-start)/timedelta(microseconds=1))

def FromFileTimeUtc(microseconds,local=False):
    from datetime import datetime,timedelta,timezone
    d=datetime(1601,1,1,tzinfo=timezone.utc)+timedelta(microseconds=microseconds)
    if local:
        return time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(d.timestamp()))
    else:
        return time.strftime('%Y-%m-%d %H:%M:%S',time.gmtime(d.timestamp()))

print(ToFileTimeUtc('2099-01-01 00:00:00'))
print(FromFileTimeUtc(ToFileTimeUtc()))

def one_cookie_for_chrome_sqlite(DOMAIN,NAME,VALUE):
    return dict(
    creation_utc=ToFileTimeUtc(),
    host_key=DOMAIN,
    name=NAME,
    value='',#现在使用了下面的encrypted_value了,对应VALUE
    path='/',
    expires_utc=ToFileTimeUtc('2099-01-01 00:00:00'),#随便写未来时间,服务器自有判断,不会以此为准.别写过去,浏览器给删了
    is_secure=0,
    is_httponly=0,
    last_access_utc=ToFileTimeUtc(),#显然最后访问时间,应该大于等于创建时间
    encrypted_value=EncryptString(key,VALUE)#上面的加密函数
    ).values()

Four, other miscellaneous

Chrome has two default related paths. As the version changes, directly create a new desktop shortcut using the –user-data-dir startup parameter:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="C:\mychrome"

The mychrome folder structure is still traditional.

2021-09-05 update

Guess you like

Origin blog.csdn.net/m0_46146791/article/details/104686060