资讯动态

FinalShell 保存密码找回:conn 文件解密与批量导出

发布时间:2026/10/2 14:50:13 来源:尧图企业网站定制
重装系统前那两天我干了一件很多人都会干的事打开 FinalShell盯着连接管理器里那一长串服务器想不起任何一台的密码。窗口里明明都保存着登录也能一键连上但那个密码框里只有一串看不懂的字符。更尴尬的是有几台机器是几年前配的root 密码只在 FinalShell 里存过一份别处压根没留档。FinalShell 这个名字在运维圈里几乎是标配轻量、免安装、带 SFTP 和实时监控很多人拿它当主力 SSH 工具。但它的密码保存机制一直有点“黑盒”你勾了记住密码下次就能免密登录可你想把密码拿出来写进文档或者迁到别的工具里就会发现自己被锁住了。这篇就把“一键找回 FinalShell 已经保存的密码”这件事从头讲到尾——FinalShell 把密码藏在哪个文件、为什么是加密的、几条不同成本的找回路线怎么选、密钥从哪里挖、脚本怎么写、以及我在实操里踩过的那些坑。不管你是刚用 FinalShell 连第一台虚拟机的学生还是手上管着上百台机器的老运维这篇里的方法都能直接照做。前提只有一个这些连接和密码是你自己的或者是你明确有权管理的设备。密码恢复这件事给自己用叫运维给别人用叫别的这个界限我会在最后一节讲清楚。1. 先看清楚FinalShell 把密码存在哪、存成什么样很多人找不到密码第一步就卡住了——根本不知道文件在哪。FinalShell 的连接信息是以单个 JSON 文件的形式落在磁盘上的一个连接对应一个文件文件名就是连接名内容是纯文本的 JSON。知道这个之后后面的操作就都有了落脚点。1.1 三分钟定位 conn 目录先说常见路径。Windows 版本多数情况在用户目录下C:\Users\你的用户名\AppData\Local\finalshell\conn\如果这里没有去 FinalShell 的安装目录下找conn文件夹也有一部分版本把配置放在程序同级目录。macOS 和 Linux 版本一般落在安装目录的finalshell/conn或者用户主目录下对应的配置目录里不同打包方式有差异。与其猜路径不如用“反向定位法”这也是我平时最常用的手段打开 FinalShell新建一个连接随便填个主机地址名字起得特别一点比如zzz_find_me保存。然后立刻在系统里按文件名搜索zzz_find_me或者按“最近修改”排序翻一下用户目录。两三分钟就能把真实路径挖出来比记路径靠谱得多。还有一个更直接的办法FinalShell 连接管理器里右键某个连接看有没有“打开所在目录”之类的选项部分版本提供这个入口。如果没有就老实用上面的搜索法。提示找到目录后先别急着改任何东西把整个 conn 文件夹复制一份到桌面当备份。后面无论做什么操作出问题都能回滚。1.2 打开 JSON看看密码字段的真身用文本编辑器打开任意一个连接文件你会看到大概这样的结构不同版本字段名有细微差别{ name: 测试机, host: 192.168.1.10, port: 22, user_name: root, password: k9Xm2pQvT7wZ1sLd4nRf8hJc, secret_key: , secret_key_password: }host、port、user_name都是明文一眼能看懂唯独password是一串看着像 Base64 的乱码。secret_key那一行存的是私钥路径或者私钥内容secret_key_password是私钥的口令同样也是加密的。这里有个判断技巧如果那串字符只包含A-Z a-z 0-9 /并且以结尾大概率是 Base64 编码后的密文如果全是0-9 a-f那就是十六进制。不同的编码方式决定你后面脚本里第一步用base64.b64decode还是bytes.fromhex先看清楚再动手能省下不少调试时间。再观察一个细节如果你有两个连接的密码其实是一样的把它们的password字段对比一下如果完全一致说明用的是不带随机向量的分组加密。这一点在验证密钥是否正确时特别有用后面会用到。1.3 为什么它不是明文一个典型的设计取舍很多人第一反应是“这软件怎么这么麻烦直接存明文不就好了”。站在开发者角度想一下就明白了明文存储意味着任何能碰到你电脑的人、任何一个能读文件的程序都能把你所有服务器的最高权限拿走。FinalShell 选择了加密存储但为了做到“免密登录”这个体验它必须能自己解密所以密钥只能硬编码在程序里。这就形成了一个很有意思的局面加密是对“随手翻文件的人”有效的对“知道原理的人”是透明的。这不是 FinalShell 独有的问题绝大多数带凭据存储的客户端工具都是这个逻辑——包括各种数据库客户端、远程桌面工具、浏览器。区别只是加密算法、密钥位置和破解难度不同。FinalShell 的做法相对朴素用的是对称加密密钥固定所以只要拿到密钥和算法参数就能还原。理解这一层之后你就知道为什么下面第三条路线是可行的了不是“绕过”了什么而是这个加密本身在设计上就不防“本机主人”。注意如果某个版本的 FinalShell 升级后改用了带随机 IV 的加密方式那么相同明文会产生不同密文解密脚本就要跟着改。所以每次大版本更新后建议重新验证一遍。2. 三条找回路线按投入产出比从低到高排不是所有场景都需要写脚本解密。先判断你的真实目标是什么——是想拿到密码明文存档还是只想让某台机器重新能登录这两个目标对应的最优解完全不同选错了会白折腾很久。2.1 路线一编辑连接界面直接看零成本先试这是最容易被忽略的一条路。打开 FinalShell 的连接管理器选中一个连接点“编辑”看密码输入框。在很多版本里这个框在编辑状态下是明文显示的你可以直接选中复制。有些版本会显示成圆点那就找找旁边有没有“显示密码”的小眼睛图标。不同版本表现不一致所以做法是每个连接都点一遍编辑看密码是明文还是圆点。如果能看到明文恭喜导出几十个连接也就是十几分钟的手工活。如果全是圆点而且找不到显示开关那就先试一个笨办法——把“记住密码”取消勾选保存再重新勾上并重新输入一次密码前提是你还记得有时候重新触发一次保存流程会让字段变成可见状态。这条路线的价值在于它不需要任何工具不需要理解加密原理也不会碰到配置文件。先用五分钟把所有连接点一遍能解决就别往下看了。我遇到过不少朋友折腾了一晚上脚本最后一问才知道他那个版本编辑界面本来就是明文——白干。提示如果连接特别多比如超过三十个手工点效率太低直接跳到第三条路线写脚本批量处理更划算。2.2 路线二不找密码直接改服务器密码换个思路如果你只是想让某台机器能重新登录根本不需要知道旧密码。只要那条连接现在还能连上因为 FinalShell 会自动填充密码登录进去把密码改掉就行。操作很直接登录后执行passwd按提示输入两遍新密码。或者用一条命令搞定适合脚本化场景echo root:YourNewPassw0rd | chpasswd改完之后回到 FinalShell 编辑该连接把密码框里的内容替换成新密码保存下次照样免密登录。整个流程不需要碰任何加密文件。这条路线的优点是简单粗暴缺点是必须满足两个前提一是连接现在还能正常建立二是你有修改该账户密码的权限。如果连接已经连不上比如服务器重装过、密钥换过或者你只有普通用户权限而目标账户是别人管的那这条就走不通。还有一个非常实际的风险改 root 密码之前先想清楚有没有别的东西依赖这个旧密码。我踩过一次坑某台测试机上的定时备份脚本里写死了数据库密码用的是同一个改完之后备份任务静默失败了一周才被发现。所以改密码之前翻一翻该机器上有没有脚本、配置文件、其他服务里引用了这个密码。2.3 路线三解密 conn 文件批量还原所有密码当连接数量多、或者需要把密码迁移到密码管理器、或者连接已经连不上但你还想拿回旧密码时就只有这条路了。目标很明确把 conn 目录里所有 JSON 的password字段批量还原成明文输出成一张表。整体流程分四步备份 conn 目录这一步别省。从 FinalShell 程序本体里挖出加密算法和密钥。写脚本遍历目录、解密每个字段。校验结果、导出成 CSV 或导入密码管理器。这四步里第二步是唯一的难点也是网上很多教程语焉不详的地方——它们直接给一个密钥让你复制但你不知道这个密钥对应哪个版本抄过来解出来是乱码就卡住了。我下面会把“怎么自己挖出来”讲透这样不管版本怎么变你都能自己搞定。3. 挖密钥从 finalshell.jar 里找到真正的加密参数FinalShell 是 Java 写的核心逻辑都打包在 jar 文件里。反编译看源码是为了确认三件事用的什么算法、什么模式、密钥是什么。这三要素齐了脚本五分钟就能写完。3.1 反编译工具的选型与准备工具首选jadx-gui它对 Java 字节码的还原度在同类工具里是最好的界面也友好支持全局字符串搜索找密钥这种活儿用它效率最高。其他选择还有 JD-GUI、CFR、Procyon效果都不错挑一个顺手的。先定位 jar 包。Windows 版 FinalShell 的安装目录下通常有一个lib或libs文件夹里面是若干个 jar核心的那个往往叫finalshell.jar或者名字里带hxi开发方标识。macOS 版在.app包里右键“显示包内容”一路进到Contents/Resources或Contents/Java下面找。判断哪个 jar 是对的有个偷懒办法按文件大小排序主程序包通常是最大的那个几百 KB 到几 MB。另外可以用系统自带的搜索在安装目录里搜*.jar然后挨个用 jadx 打开看包结构找到com.hxi开头的包基本就对了。注意反编译仅用于搞清楚自己的客户端是怎么存密码的属于本机软件分析。分析出来的代码和密钥自己留着用就行别往外传原因在最后一节说。3.2 定位加解密相关的类打开 jadx-gui加载 jar 之后用全局搜索一般是 CtrlShiftF 或菜单里的 Search Text依次搜几个关键词DES直接定位到对称加密算法相关的类。CipherJava 加解密的标准入口类。decrypt解密方法名。setPassword/getPassword密码字段的读写位置。我一般先搜Cipher.getInstance因为这行代码会明确写出算法、模式和填充方式比如Cipher.getInstance(DES/ECB/PKCS5Padding)。找到这行算法三要素里的前两个就确定了。然后从这行往上翻找密钥是从哪来的——通常是一个静态常量或者一个DESKeySpec构造时传进去的字节数组也可能是一个字符串调.getBytes()。那个字符串就是你要的东西。有些版本会把密钥再包一层比如从某个常量类里读取或者做了简单的字符位移。遇到这种情况就顺着调用链往上跟jadx 的“查找引用”功能右键方法名 → Find Usage很好用能一路追到源头。找到之后把那个密钥字符串记下来。注意两个细节一是它必须正好 8 字节DES 的密钥长度就是 8 字节不足会报错或自动补位二是注意它的字符编码ASCII 字符直接数一下长度如果密钥里出现了中文或者特殊符号要确认程序用的是不是 UTF-8 转字节。3.3 确认算法三要素与填充方式汇总一下你需要在源码里确认的四件事项目常见取值怎么确认算法DES / AES / 3DES看Cipher.getInstance的第一个参数加密模式ECB / CBC同上/分隔的第二段填充方式PKCS5Padding / NoPadding同上第三段密钥8 字节字符串DES看DESKeySpec或SecretKeySpec的构造参数如果模式是 ECB就不需要 IV初始化向量脚本最简单也是最常见的情况。如果源码里出现IvParameterSpec说明是 CBC 模式那就还得把 IV 也挖出来——通常在密钥常量附近。还有一个验证技巧前面提过这里派上用场ECB 模式下相同明文必然产生相同密文。所以你把两个已知密码相同的连接文件拿来比对密文字段完全一致就基本能确认是 ECB同时也能验证你手上的密钥是对的。反过来说如果两个相同密码的密文不一样那就是 CBC 或者带了随机因子脚本要相应调整。确认完这四件事就可以进入下一步。整个过程熟练之后十分钟内能搞定第一次可能要花半小时。4. 写脚本把整个 conn 目录批量解出来到了这一步剩下的都是体力活了。我给出两种方案Python 脚本适合快速批量处理Java 方案适合你不想自己实现算法、直接复用原程序逻辑的情况。4.1 环境准备与依赖Python 方案需要一个加密库。我推荐pycryptodome它对 DES 的支持完善安装也简单pip install pycryptodome注意别装成老旧的pycrypto那个包在新版 Python 上编译经常出问题。装完之后from Crypto.Cipher import DES能正常导入就对了。另外准备一个输出目录脚本会把解密结果写成一个 CSV。CSV 的好处是 Excel、WPS 都能直接打开也方便后面导入到 KeePass、Bitwarden 这类密码管理器。4.2 Python 脚本主体下面是结构完整的脚本把密钥那一行替换成你自己挖出来的值就能跑import base64 import csv import json from pathlib import Path from Crypto.Cipher import DES # 从 finalshell.jar 里挖出来的 8 字节密钥 KEY byour_key # 替换成你自己的 CONN_DIR Path(rC:\Users\你的用户名\AppData\Local\finalshell\conn) OUT_CSV Path(finalshell_passwords.csv) def decrypt_password(cipher_text: str) - str: 把 JSON 里的 password 字段还原成明文 if not cipher_text: return try: raw base64.b64decode(cipher_text) except Exception: # 如果密文是十六进制换这一行 raw bytes.fromhex(cipher_text) cipher DES.new(KEY, DES.MODE_ECB) plain cipher.decrypt(raw) # 去掉 PKCS5 填充最后一个字节表示填充长度 pad plain[-1] if 1 pad 8: plain plain[:-pad] return plain.decode(utf-8, errorsreplace) def main(): rows [] for file in sorted(CONN_DIR.glob(*.json)): try: data json.loads(file.read_text(encodingutf-8)) except Exception as exc: print(f[跳过] {file.name}: {exc}) continue rows.append({ 连接名: data.get(name, ), 主机: data.get(host, ), 端口: data.get(port, ), 用户名: data.get(user_name, ), 密码: decrypt_password(data.get(password, )), 私钥口令: decrypt_password(data.get(secret_key_password, )), }) with OUT_CSV.open(w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows) print(f共处理 {len(rows)} 条连接结果已写入 {OUT_CSV}) if __name__ __main__: main()脚本里有两个细节值得单独说。第一个是errorsreplace。万一密钥不对或者填充处理有偏差解码会抛异常导致整批中断用 replace 至少能看到部分结果方便判断问题出在哪。如果输出的密码全是?和乱码方块说明密钥错了回去重新挖。第二个是utf-8-sig。这是为了 Excel 能正确识别中文连接名不带 BOM 的话打开 CSV 经常是乱码。很多人在这里卡半天以为是解密错了其实只是编码问题。4.3 Java 方案直接把原程序的解密类抠出来用如果你不想自己实现算法或者挖出来的算法不是标准的 DES还有一条更稳的路直接用 jadx 把反编译出来的解密工具类导出成 Java 源文件改一下包名编译运行。操作步骤是在 jadx-gui 里找到那个包含解密方法的类右键选择“Save as Java”导出成.java文件。然后把类名、包名改成你自己的删掉无关的依赖保留核心解密方法。写一个 main 方法读 JSON 调它就行。这个方案的优势是百分之百和程序本身行为一致不存在“算法细节理解偏差”的问题。缺点是得配一个 Java 编译环境稍微麻烦一点。我一般先用 Python 试解出来是乱码又找不到原因时再换 Java 方案兜底。还有一种更“野”的做法写个小 Java 程序把finalshell.jar加到 classpath 里用反射直接调用它的解密静态方法。这样连源码都不用抠// 示意代码类名和方法名要用 jadx 里看到的真实名字替换 URLClassLoader loader new URLClassLoader( new URL[]{new File(finalshell.jar).toURI().toURL()}); Class? clazz loader.loadClass(com.hxi.xxx.CipherUtil); Method method clazz.getMethod(decrypt, String.class); Object result method.invoke(null, cipherText);反射的好处是不依赖具体版本只要方法签名没变就能用。坏处是类名和方法名得准确还是得先反编译确认一遍。4.4 结果校验与整理导出脚本跑完第一步不是马上把 CSV 拖进密码管理器而是做几项校验。先看行数对不对。脚本最后会打印处理条数跟连接管理器里的连接数量对一下少了说明有的 JSON 结构不一样被跳过了回去看看那些文件。再看每一行的密码字段是不是正常的 ASCII 字符串——正常的密码不会是一堆方块或者问号。最后挑两台机器实际登录验证一下这是最硬的校验能连上就说明一切正确。验证通过之后把 CSV 导入密码管理器。KeePass 支持导入 CSVBitwarden 也支持导入时注意映射一下字段把“主机”和“用户名”组合成条目标题方便以后搜索。导完立刻把 CSV 删掉这是个习惯问题明文表格散落在磁盘上和你当初纠结加密存储的初衷就矛盾了。注意操作 conn 文件之前一定要把 FinalShell 完全退出。程序在运行时会读取内存里的配置退出时可能把内存内容重新写回文件覆盖掉你的改动。我有一次改完文件发现没生效就是因为 FinalShell 还在后台挂着。5. 实操踩坑记录与常见问题速查表上面讲的是顺路走通的流程实际操作里状况比这多得多。这一节把我和身边同事踩过的坑整理成速查表遇到问题先来这里对号入座能省下大量搜索时间。5.1 常见问题速查表现象大概率原因处理方式解出来全是乱码或问号密钥不对或版本换了密钥重新反编译挖密钥注意版本差异脚本读取 JSON 报错密文是十六进制不是 Base64把b64decode换成bytes.fromhex解密时抛填充异常密文被截断或密钥长度不对确认密钥正好 8 字节检查字段是否完整复制CSV 打开中文乱码编码没带 BOM写文件时用utf-8-sig改完文件 FinalShell 还是旧密码程序未退出退出时覆盖了改动完全退出程序后再改改完再启动相同密码密文不一样不是 ECB 模式可能有 IV反编译找IvParameterSpec取 IV 参与解密脚本处理后条数变少部分 JSON 字段名不同或被跳过打印跳过日志逐个检查异常文件连接数量很多手工核对太累没做自动化校验按主机名排序随机抽 10% 实际登录验证挖到的密钥字符串长度不是 8取错了常量可能是 ID 或盐值顺着DESKeySpec的调用往上追真正的密钥解出的密码能用但总觉得不对明文里带了不可见字符打印时用repr()看一眼去掉首尾空白5.2 几条只有踩过才知道的经验第一条关于备份。把整个 conn 目录复制一份再动手这个动作只要十秒但能救你一整天。我见过有人学着教程直接改文件结果脚本写错了把字段覆盖成空字符串所有连接的密码全丢只能挨个去服务器重置。备份之后任何操作失误都能秒回滚。第二条关于版本差异。同样的软件不同版本、不同渠道下载的安装包内部实现可能不一样网上抄来的密钥经常对不上。所以“自己挖”这个能力比“记住密钥”重要得多。我现在的习惯是每次 FinalShell 大版本升级后先拿一个测试连接验证一下老脚本还能不能跑不能跑就重新挖一次五分钟的事。第三条关于导出之后的管理。解出来的明文密码最好当场导入密码管理器然后把中间产物清掉。我见过太多人把passwords.csv往桌面一扔就是半年这和把密码写在便利贴上贴在显示器边框上没有任何区别。既然选择了加密存储工具就别在最后一步把加密的成果亲手作废。第四条是关于批量解密的边界。脚本一旦写好跑一个连接和跑一百个连接的成本是一样的这时候人容易“顺手”把别人的东西也解一遍。这个念头要按死具体原因下一节说。6. 关于安全边界和长期做法技术本身是中性的用在哪里决定了它的性质。这一节不讲大道理只讲几条实际工作中应该守住的东西以及我这些年形成的习惯。6.1 这次操作的安全边界这套方法适用的场景只有一个恢复你自己拥有的、或者你被明确授权管理的设备的连接凭据。判断标准很简单——这些连接是你自己建的、这些服务器是你负责的、或者你是团队里被授权管理这批机器的人。满足这三条里的任意一条你就是在做本职工作。反过来任何情况下都不应该拿这套方法去处理不属于你的配置文件、共享电脑上别人的账户、或者来路不明的 conn 目录。工具本身不带立场但使用者的行为有边界。如果你是在公司环境里操作涉及生产环境的凭据记得走内部的配置管理流程别自己解密完了随手扔在个人电脑上。另外一点解密脚本、挖出来的密钥、导出的 CSV这些东西都只在你的本机存在不要发到群里、不要传到公共网盘、不要塞进任何公共仓库。原因很实在——这相当于把你所有服务器的钥匙复制了一份放在公开场合风险有多大不用我多说。6.2 长期该怎么做才不用每次都折腾找回密码这件事本身就是一次“流程欠债”的暴露。更好的做法是从源头上就不再依赖单个工具的密码存储。第一把凭据集中管理。用 KeePass、Bitwarden 这类密码管理器统管所有服务器凭据FinalShell 里的“记住密码”只当作便利功能不作为唯一副本。这样任何一台机器换工具、重装系统凭据都不受影响。我现在是密码管理器里存一份FinalShell 里存一份两边定期同步。第二能用密钥登录就用密钥。SSH 密钥对不仅能免密登录还能在服务器上禁用密码登录安全性提升一大截。FinalShell 对私钥的支持很完整secret_key字段就是干这个的。用上密钥之后“找回密码”这个需求本身就消失了一大半。第三换机器前做一次导出。重装系统或者换电脑之前跑一遍本文的脚本把所有连接和密码导成 CSV导入密码管理器确认无误之后再格盘。这个动作花十分钟但能保证你换完机器之后不用满世界找密码。我就是被这个坑教育过两次之后才养成习惯的。如果这篇文章里你只能记住一件事我希望是最后这条密码存两份一份在工具里图方便一份在管理器里图安心。找回密码的手段偶尔用一次是应急天天用就说明流程该改了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑