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)
Local State file (json, storing various types, key is also in it)
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,,,)
Third, construct write-back
(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