news 2026/10/16 13:39:45

Python实现本地密码查看器:从浏览器凭据解密到可回显调试开关

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python实现本地密码查看器:从浏览器凭据解密到可回显调试开关

简介:这是一款面向Windows用户的星号密码查看工具,适合需要找回本机已保存密码的普通用户与运维人员。它支持WinXP、Win7等主流Windows平台,既能查看Outlook、FTP等桌面软件窗口中的星号密码,也能读取Gmail、Yahoo等网页表单里隐藏的密码,还可用于找回宽带拨号密码以及路由器中存储的ADSL账号密码,覆盖场景较为全面。压缩包内共2个文件,包含1个htm说明文档与1个exe可执行程序,整体体积约385KB,属于轻量级绿色工具,运行速度快、操作门槛低,无需复杂配置即可上手。目前已有190人学习下载,说明其在密码找回类工具中具有一定实用价值。对于忘记本机保存密码、需要快速定位并还原星号内容的读者,这份资源提供了直接可用的解决方案,配合说明文档可快速理解使用方式,省去自行摸索的时间。

1. 密码查看器:从“忘了密码”到“看清密码”的那层窗户纸

做运维或者经常折腾本地环境的人,大概率遇到过这种场景:浏览器里保存了一堆站点凭据,时间一长只记得用户名,密码那栏全是圆点;或者自己写的小工具把配置里的敏感字段做了掩码,结果调试时死活想不起来原始值是什么。这时候搜“密码查看器”,心里想的其实不是去偷谁的密码,而是想把自己有权访问的那份凭据还原成明文看一眼。这个方向本身不复杂,核心就三件事:找到凭据存在哪、搞清楚它被什么算法保护、用对应方式解出来。适合两类人:一类是刚接触本地凭据管理的开发者,想弄明白浏览器和系统到底怎么存密码;另一类是负责内部工具的同学,需要在合规前提下给自己的配置加一个“可回显”的调试开关。下面按“原理—实现—避坑—进阶”的顺序拆开讲,代码都能直接跑。

2. 凭据到底存在哪:三类常见存储位置与读取入口

2.1 浏览器保存的登录凭据:SQLite 加系统密钥

主流浏览器把站点密码存在一个 SQLite 数据库里,字段通常是 origin_url、username_value、password_value。关键点在于 password_value 不是明文,而是被操作系统级别的密钥加密过。在 Windows 上走的是 DPAPI,在 macOS 上走的是 Keychain,Linux 上则依赖桌面环境的密钥环。这意味着你光拿到数据库文件没用,必须同时具备当前用户会话的上下文,才能解密。很多人第一次拿到 Login Data 这个文件,用 SQLite 打开发现 password_value 是一串二进制,就以为加密不可逆,其实只是没走对解密入口。

读取入口分两种:一种是直接读数据库文件,另一种是通过浏览器提供的调试接口。前者适合离线分析,后者适合在浏览器运行状态下取当前会话数据。我一般优先用直接读文件的方式,因为可控性强,不依赖浏览器是否开着。

2.2 系统级凭据管理器:按用户隔离的保险箱

操作系统自带的凭据管理组件,通常按“用户 + 应用”两个维度隔离。Windows 的 Credential Manager、macOS 的 Keychain、Linux 的 Secret Service,都是这个思路。它们的共同特点是:同一台机器上,不同用户账户之间的凭据互不可见;同一个用户下,不同应用写入的条目也互相隔离。所以做密码查看器时,第一件要确认的事是“当前进程跑在哪个用户上下文里”。如果你用管理员权限去读普通用户的凭据,大概率读不到,这不是 bug,是设计如此。

2.3 应用自建配置:掩码字段与可逆编码的边界

很多内部工具会把数据库连接串、第三方 API Key 写在配置文件里,为了“看起来安全”做了掩码,比如只显示前四位和后四位。这种掩码如果只是展示层做的,原始值还在内存或配置里,那查看器只需要在展示层加一个切换开关即可。但如果原始值本身做了可逆编码(比如 Base64 或简单的异或),那就需要对应的解码逻辑。这里要区分清楚:可逆编码不是加密,它只是换了种表示形式,任何拿到编码串的人都能还原。做查看器时,对这类字段要明确标注“仅编码,非加密”,避免使用者误以为它有多安全。

3. 用 Python 写一个最小可用的本地密码查看器

3.1 环境准备与依赖选择

先明确目标:在 Windows 上读取当前用户 Chrome 保存的站点密码,并打印出明文。需要两个能力:读 SQLite 数据库、调用 DPAPI 解密。Python 标准库自带 sqlite3,DPAPI 可以通过 ctypes 调用 crypt32.dll 里的 CryptUnprotectData 函数,不需要额外装第三方包。如果你在 macOS 上,对应的是 security 命令行工具或 Keychain API;Linux 上则要看具体密钥环实现。下面以 Windows 为例,因为它的路径和调用方式最固定,适合作为第一个跑通的版本。

import os import sqlite3 import ctypes from ctypes import wintypes # 定位当前用户 Chrome 的 Login Data 文件 # 注意:Chrome 运行时会锁库,建议先复制一份再读 local_app_data = os.environ['LOCALAPPDATA'] login_db = os.path.join( local_app_data, 'Google', 'Chrome', 'User Data', 'Default', 'Login Data' ) print('目标数据库:', login_db)

这段代码只做了一件事:拼出数据库路径。LOCALAPPDATA 是 Windows 上每个用户独立的环境变量,所以天然满足“当前用户上下文”这个前提。如果你要读其他浏览器的数据,把中间那几段路径换掉即可,比如 Edge 是 Microsoft\Edge\User Data\Default\Login Data。参数上唯一要注意的是 Default 这个目录名,它对应的是浏览器里的第一个用户配置,如果你有多个配置,需要改成 Profile 1、Profile 2 这样的名字。

3.2 读取 SQLite 并识别加密字段

拿到路径后,不要直接对原文件开连接,因为浏览器进程可能持有文件锁。稳妥做法是先复制到临时目录,再对副本操作。这一步很多人省掉,结果遇到 database is locked 就卡住,以为是代码问题,其实是文件被占用。

import shutil import tempfile # 复制一份到临时目录,避免锁库 tmp_dir = tempfile.mkdtemp() tmp_db = os.path.join(tmp_dir, 'Login Data') shutil.copy2(login_db, tmp_db) conn = sqlite3.connect(tmp_db) cursor = conn.cursor() cursor.execute( 'SELECT origin_url, username_value, password_value FROM logins' ) rows = cursor.fetchall() print('共读取到', len(rows), '条记录') for url, user, pwd in rows[:3]: print(url, user, type(pwd), len(pwd) if pwd else 0)

这里查的是 logins 表,字段名在不同浏览器版本里可能有细微差异,但 origin_url、username_value、password_value 这三个基本稳定。打印 type(pwd) 会看到它是 bytes,长度通常几十到上百字节,这就是加密后的密文。如果你看到的是空值,说明该条记录没有保存密码,或者浏览器用了新的加密方案。参数上,rows[:3] 只是预览,实际使用时可以全量遍历,但要注意输出到终端时做脱敏,避免明文直接刷屏。

3.3 调用系统解密接口还原明文

Windows 上解密靠 CryptUnprotectData,它接收一段 DATA_BLOB 结构,返回解密后的 DATA_BLOB。用 ctypes 定义好结构体后,调用逻辑很直接。关键点是:调用必须发生在当前用户会话中,且进程不能是模拟其他用户的身份。

class DATA_BLOB(ctypes.Structure): _fields_ = [ ('cbData', wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_char)) ] def decrypt_dpapi(encrypted_bytes): blob_in = DATA_BLOB( len(encrypted_bytes), ctypes.cast( ctypes.create_string_buffer(encrypted_bytes), ctypes.POINTER(ctypes.c_char) ) ) blob_out = DATA_BLOB() if ctypes.windll.crypt32.CryptUnprotectData( ctypes.byref(blob_in), None, None, None, None, 0, ctypes.byref(blob_out) ): data = ctypes.string_at(blob_out.pbData, blob_out.cbData) ctypes.windll.kernel32.LocalFree(blob_out.pbData) return data.decode('utf-8', errors='replace') return None for url, user, pwd in rows[:3]: plain = decrypt_dpapi(pwd) print(url, user, plain)

decrypt_dpapi 接收密文字节,返回明文字符串。CryptUnprotectData 的第二个参数是描述信息,传 None 即可;最后一个参数是 flags,传 0 表示默认行为。解密成功后必须调用 LocalFree 释放系统分配的内存,否则会泄漏。返回的明文用 utf-8 解码,errors='replace' 是为了防止个别非标准编码导致整个脚本崩掉。到这里,一个最小可用的密码查看器就成型了:定位数据库、复制、查询、解密、输出。整个链路没有黑匣子,每一步都能单独验证。

4. 避坑与排查:密码查看器最容易翻车的五个点

4.1 现象:解密返回 None 或乱码

原因通常有三种:一是进程不在目标用户的会话里,比如你用系统服务或计划任务跑,DPAPI 拿不到用户主密钥;二是浏览器版本较新,对密文做了额外封装,比如在前面加了版本前缀;三是复制数据库时文件不完整,密文被截断。解决方式是先确认进程用户身份,再检查密文长度是否合理,最后对比浏览器版本和已知的存储格式。如果密文开头有 v10 或 v11 这样的前缀,需要先剥离前缀再解密。

4.2 现象:数据库能打开但 logins 表为空

这多半是因为你读的是副本,而副本是在浏览器写入过程中复制的,导致部分页未落盘。另一个可能是浏览器启用了“不保存密码”策略,或者用户手动清理过。排查时可以先在浏览器里确认该站点确实保存了密码,再检查复制时机。稳妥做法是在浏览器完全退出后再复制,或者用 SQLite 的备份 API 做在线备份。

4.3 现象:脚本在别人机器上跑不通

这是权限边界问题,不是代码问题。DPAPI 的设计就是“同一用户可解,跨用户不可解”。如果你把脚本和数据库一起拷到另一台机器,即使用户名相同,也解不开,因为主密钥跟机器和用户账户绑定。遇到这种情况,不要试图绕过,而应该回到目标机器上、以目标用户身份运行。这也是为什么很多“密码查看器”工具强调必须在本机本用户下使用。

4.4 现象:输出到终端时明文被截断或转义

有些密码里包含特殊字符,比如反斜杠、引号、控制字符,直接 print 可能显示不全或触发终端转义。解决方式是在输出前做一次 repr 或 json.dumps,把不可见字符转成可读形式。如果是要写入文件,建议用 utf-8 编码并显式指定 newline='',避免换行符被转换。

4.5 现象:杀毒软件报警

读取浏览器密码数据库并调用解密接口,这个行为模式在一些安全软件看来属于敏感操作。如果你是在做自己的调试工具,建议把脚本放在非系统目录、避免频繁全量读取,并在代码里加注释说明用途。如果是给团队用,最好走内部签名或白名单,而不是让每个人去关杀软。这一点属于工程规范,不是技术难点,但踩过一次就知道有多麻烦。

5. 进阶:把查看器做成可验证、可回显的调试开关

5.1 用校验和确认解密结果可信

解密出来的明文对不对,不能只看“像不像”。一个简单办法是:在保存密码时同时记录一个校验值,比如对明文做一次 SHA-256 取前 8 位,查看器解密后再算一次,比对一致才输出。这样即使解密逻辑有细微偏差,也能立刻发现。对于浏览器凭据这种你无法控制写入过程的场景,可以用“重新登录验证”作为最终确认,但那是另一条路径了。

import hashlib def checksum(text): return hashlib.sha256(text.encode('utf-8')).hexdigest()[:8] # 假设保存时记录了 checksum,解密后比对 plain = decrypt_dpapi(pwd) if plain and checksum(plain) == saved_checksum: print('校验通过:', plain) else: print('校验失败,可能解密有误')

这个思路适合自建配置的场景。对于浏览器凭据,你拿不到保存时的校验值,但可以用“同一站点多次解密结果一致”作为辅助判断。参数上,截取前 8 位是为了可读性,实际做完整性校验时应该用完整摘要。

5.2 给内部工具加一个受控的回显模式

如果你在维护内部工具,与其让每个人去翻浏览器数据库,不如在工具里加一个显式的回显开关。默认掩码,只有传入特定参数且当前用户有权限时才回显明文。实现上可以用环境变量加命令行参数双重确认,避免误触发。

import argparse import os parser = argparse.ArgumentParser() parser.add_argument('--reveal', action='store_true') args = parser.parse_args() # 双重确认:命令行显式指定,且环境变量允许 allow = os.environ.get('TOOL_ALLOW_REVEAL') == '1' if args.reveal and allow: print('明文:', plain) else: print('掩码:', plain[:2] + '*' * (len(plain) - 4) + plain[-2:])

这里 --reveal 是显式意图,TOOL_ALLOW_REVEAL 是环境级开关,两者同时满足才回显。这样即使有人误传了 --reveal,没有环境变量也不会泄露。参数上,掩码保留前两位和后两位是为了方便核对,具体保留几位可以根据字段长度调整。

5.3 跨平台读取的差异与统一封装

Windows、macOS、Linux 三端的解密入口完全不同,但上层逻辑可以统一:定位数据库、查询、解密、输出。差异封装在一个 decrypt 函数里,根据 sys.platform 分派。macOS 上可以调用 security find-generic-password,Linux 上则要看是否装了 secret-tool。这样你的查看器主体不用改,换平台只换底层实现。

平台存储位置解密方式关键前提
WindowsLOCALAPPDATA 下浏览器目录DPAPI当前用户会话
macOS~/Library/Application SupportKeychain当前用户解锁
Linux~/.config 下浏览器目录Secret Service密钥环已解锁

这张表建议放在代码注释里,方便后来人快速定位。实际写的时候,每个平台单独测一遍,不要假设“逻辑一样所以结果一样”。

5.4 我踩过的一个坑:别在循环里反复初始化解密上下文

早期版本里,我在遍历每条记录时都重新加载一次解密库,结果数据量一大就明显变慢。后来改成模块级初始化一次,循环里只做解密调用,速度差了好几倍。这个坑不涉及原理,纯粹是工程习惯:凡是初始化成本高的对象,都放到循环外面。另一个习惯是,任何涉及凭据的输出,先在内存里做一次脱敏预览,确认无误再决定是否落盘。这两条经验让我在后面做类似工具时少走了很多弯路。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 13:38:41

OSPF邻居状态机详解:从Down到Full的七步跃迁与排错逻辑

简介:本资源是面向备考华为HCIP-Datacom-Core Technology V1.0认证(考试编号H12-821/H12-831)的学习者整理的系统性笔记,聚焦网络安全、路由协议、交换技术等核心考点,适用于网络工程师进阶提升与考前冲刺。内容覆盖OS…

作者头像 李华
网站建设 2026/10/16 13:38:06

abap2xlsx 5个Demo快速实现SAP报表Excel导出

简介:在 SAP 系统与 Excel 的交互开发中,ABAP2XLSX 是常用的开源工具,能以编程方式生成 .xlsx 文件。这份资源针对 abap2xlsx 收集了 5 个不同应用场景的演示程序:YDemo_01 演示最基础的工作表创建、表头写入与数据填充&#xff1…

作者头像 李华
网站建设 2026/10/16 13:37:04

IDEA Services面板配置指南:SpringCloud多服务启动管理

1. 为什么一个“窗口面板”值得单独写一篇笔记 如果你正在跟着教程搭建一套基于 SpringCloud 2020 版本的微服务示例工程,大概率会遇到这样一个场景:辛辛苦苦把注册中心、网关、若干个业务服务都建好了,每个服务都能单独启动,但每…

作者头像 李华
网站建设 2026/10/16 13:36:31

DASSIDirect 3.0 连接西门子 PLC 全解析:从链路原理到配置避坑

简介:DASSIDirect3.0 是 Intouch 体系下用于访问西门子 PLC 的通信驱动软件,面向工业自动化工程师、HMI 组态开发者及现场调试人员,解决 Intouch 人机界面与西门子 S7 系列 PLC 之间数据交互与集成的问题。其核心基于 suiteLink 专有协议&…

作者头像 李华
网站建设 2026/10/16 13:32:36

纯C手写GIF解码器:LZW解压与调色板实战

简介:这份资源面向嵌入式开发与C语言学习者,聚焦GIF动画解码这一实用技能,帮助读者理解图像格式解析与无损压缩算法的落地实现。包内共2个文件,包含1个c源文件与1个h头文件,压缩包约5KB,c文件承载解码核心逻…

作者头像 李华