你如果管理超过十台服务器,肯定遇到过这种尴尬:旧电脑淘汰或重装系统时,FinalShell 里存了一堆服务器连接记录,很多密码只有 FinalShell 自己知道,你却完全不记得原始密码是什么。前阵子我帮同事做工作机迁移,他这台用了三年的 FinalShell 里躺着三十多台机器的连接配置,急着要导到新电脑上。来回折腾了一圈,踩了不少坑,也把隐藏的导出逻辑彻底摸透了。
先给结论:FinalShell 没有提供“一键导出所有明文密码”的按钮,但它所有的连接信息和密码都保存在本地,只要你找到配置文件、理解它的存储逻辑,再配合一小段解密脚本,就能把密码完整还原出来。这篇就把从界面操作、配置文件定位,到解密脚本写法和安全注意事项全部讲清楚。
1. 为什么没有“一键导出密码”?先搞懂 FinalShell 的数据存储机制
想顺利导出密码,第一件事不是到处找导出按钮,而是搞清楚 FinalShell 到底把密码放在哪里、以什么格式存放。我也是这次迁移时才认真翻了一遍它的数据目录,才发现它的存储逻辑其实很直白。
1.1 连接配置文件的真实位置和文件结构
FinalShell 的连接配置不是存在云端,而是纯本地文件。不同操作系统下,路径稍有差异:
| 操作系统 | 配置目录 | 说明 |
|---|---|---|
| Windows | %APPDATA%\finalshell |
一般是 C:\Users\你的用户名\AppData\Roaming\finalshell,部分老版本在 %LOCALAPPDATA%\finalshell |
| Linux | ~/.finalshell |
用户主目录下的隐藏目录 |
| macOS | ~/Library/Application Support/finalshell |
与 Windows 风格不同,需要手动前往 |
如果你在自己机器上找不到,最快的办法是在文件管理器里直接搜索 connections.json,这个文件名基本是固定的。我建议用一个笨但可靠的方法定位:在 FinalShell 的设置里随便改一个显示主题或字体大小,看哪个目录下的配置文件时间戳变了,那个目录就是数据目录。
这个目录里通常会有几类文件:
connections.json:核心连接列表,保存了所有 SSH 连接信息other.json:界面设置、窗口布局等recent.json:最近访问记录server_list.json:部分版本是它,作用与 connections.json 相同
真正要紧的就是 connections.json 这类存连接的文件。
1.2 JSON 里每一个字段代表什么
用文本编辑器打开 connections.json,你会看到一长串 JSON 数组。每条连接记录的结构大致如下:
json复制[
{
"id": "8f6d9d2e-1f2a-4b18-9c73-0b82b63169e1",
"name": "生产环境-Web01",
"host": "192.168.1.101",
"port": 22,
"username": "root",
"password": "U2FsdGVkX1xxxxxxxxxxxx",
"auth_type": "PASSWORD",
"private_key": "",
"passphrase": "",
"group": "生产服务器",
"proxy_id": "",
"description": ""
}
]
字段含义用表格列一下:
| 字段名 | 含义 | 迁移时需要吗 |
|---|---|---|
| id | 连接唯一标识,UUID 格式 | 保留避免配置混乱 |
| name | 在连接列表里显示的名称 | 需要 |
| host | 服务器 IP 或域名 | 需要 |
| port | SSH 端口,默认 22 | 需要 |
| username | 登录用户名 | 需要 |
| password | 加密后的密码字段 | 需要,重点处理对象 |
| auth_type | 认证方式,PASSWORD 或 KEY | 需要 |
| private_key | 使用密钥登录时的私钥路径或内容 | 按需 |
| group | 分组名,便于归类 | 建议保留 |
可以看到,password 字段并不是明文。这一条要扭清楚:它经过了一定程度的混淆加密,但加密强度并不高,属于可逆的算法,后面会详细拆解。
1.3 密码的混淆方式:不是明文,但不是强加密
很多人以为 FinalShell 的密码就是 Base64 编码那么简单,实际上它的处理过程稍微绕了一点。社区公开的逆向分析结果显示,FinalShell 在保存密码时,会先把密码进行异或(XOR)运算,再做 Base64 编码。异或的密钥是一个固定字符串,不同版本之间会有差异。
为什么要说它“不是强加密”?因为真正的强加密设计中,密钥应该独立保存且无法通过可逆脚本直接还原。而 FinalShell 的密钥就写在自己的程序文件里,逆向分析或动态调试都能拿到。换句话说,任何能运行程序的人都可以提取出密钥,所以这种所谓加密只能防“手贱打开 JSON 文件直接看到明文”的人,对具有一定技术能力的人基本是透明的。
知道这一点后,密码导出这个任务就变成了三步:
- 找到 connections.json
- 读取 password 字段
- 用对应版本的密钥做 Base64 解码 + XOR 运算还原明文
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 换电脑/重装系统前,最稳的备份导出路线
很多用户一听到要写脚本就头大。如果你只是想在换电脑或者重装系统时保留连接记录,其实不一定非得解密密码,走 FinalShell 自带的导入导出路线就够了,前提是你新旧电脑装的是同一个大版本。
2.1 界面里的导入导出功能怎么用
FinalShell 的设置中心里提供了配置备份功能,这是我在迁移时最先试的路子。操作路径如下:
- 打开 FinalShell,点击右上角的“设置”图标,在界面顶部能看到“导入导出”一栏。
- 选择“导出配置”,程序会生成一个
.json格式的备份文件,包含了所有连接记录、分组设置、界面布局。 - 把这个文件拷贝到新电脑,同样进入“设置 -> 导入导出”,选择“导入配置”,文件路径指向刚才的备份文件。
这个过程会把你所有窗口配置、连接信息一并迁移过去,包括保存的密码字段。密码不会变成明文,但连接可用性保留了下来。
不过要特别提醒一点:当你使用“密码登录”方式保存了连接,导出备份后,在新电脑导入时通常不需要重新输入密码就能直连。但如果你平时用 auth_type 为 PASSWORD 的方式连接,而备份文件中的 password 字段为空,那导入后大概率要重新输入一次密码。
2.2 手动备份整个配置目录,最笨但最全
界面导出虽然方便,但有一个隐藏问题:它可能不会带上你自定义的密钥文件、端口转发规则等额外配置。更稳妥的办法是直接把整个配置目录打包。
拿 Windows 举例:
bash复制# 先关闭 FinalShell,避免文件占用
cd %APPDATA% && tar -czvf finalshell_backup.tar.gz finalshell
或者直接在资源管理器里选中 finalshell 目录,压缩成 zip,拷走就行。
Linux 下同理:
bash复制tar -czvf finalshell_backup.tar.gz ~/.finalshell
这一步的额外收获是,如果 FinalShell 程序本身装到某些临时目录,重装后可能出现版本不匹配的情况,但完整配置目录能帮你把分组、历史连接、SFTP 书签这些全部搬到新环境。
手动备份目录有一点要注意:务必在退出 FinalShell 后再拷贝,否则正在写入的缓存文件可能损坏。我有一次就是开着程序直接复制,结果拿到新电脑导入时提示 JSON 解析失败,最后只能从时间戳最早的备份去恢复。
2.3 新电脑上怎么恢复连接列表和密码
恢复方式取决于你采用了哪种导出方案。
如果是界面导出,新电脑上直接“导入配置”即可,基本上是傻瓜式操作。如果是手动打包整个目录,需要按以下步骤:
- 安装好对应版本的 FinalShell(尽量与旧电脑版本一致或更高)。
- 不要先启动程序,如果已经启动并生成了默认配置,先退出。
- 把你备份出来的目录覆盖到对应位置(Windows 是
%APPDATA%\finalshell)。 - 重新启动 FinalShell,连接列表会完整出现在侧边栏。
我推荐这个做法的原因很简单:它保留的不仅仅是密码,而是完整的“工作现场”。分组文件夹、窗口布局、主题、甚至上次会话的标签页都能回来,对生产力影响最小。
3. 从配置文件里提取并解密保存的密码
界面导入导出能完成整体迁移,但有些场景更特殊:你想拿到保存密码的明文,比如用来配置其他工具,或者 FinalShell 新版不兼容旧配置,你只能手动重建连接。这时就需要直面解密环节了。
3.1 读出加密字段,先做 Base64 解码
我拿一台 Windows 机器上的实际 connections.json 来演示。用文本编辑器打开后,找到任意一条记录,看到 password 字段是一串类似这样的内容:
code复制U2FsdGVkX1+abcdefghijklmnopqrstuvwxyz123456
不要先去猜它是什么,先做 Base64 解码。用 Python 一行就能完成:
python复制import base64
encoded = "U2FsdGVkX1+abcdefghijklmnopqrstuvwxyz123456"
raw = base64.b64decode(encoded)
print(raw.hex())
输出结果是十六进制字节。此时如果你看到头部出现了可读字符串,那说明它可能用了更简单的拼接式编码;如果全是乱码,就说明做了异或或者对称加密,需要进入下一步。
Base64 解码只是第一步,这本质上相当于把外层包装撕掉,里面才是真正经过算法处理的密文。
3.2 基于异或原理的 Python 解密脚本
根据社区逆向的结果,FinalShell 早期版本保存密码时,使用了一个固定的字符串作为异或密钥。异或运算的特点是对称性:同一个密钥加密和解密结果相同。所以只要拿到正确的密钥,还原密码只是几个字节的操作。
我结合常见逆向脚本,整理了一个通用型 Python 脚本。需要注意,不同大版本密钥可能不同,脚本里我把密钥设置成了一个变量,方便你手动调整。
python复制import json
import base64
import sys
import os
def decrypt_password(encoded, key):
"""
还原 FinalShell 保存的连接密码。
key 为异或密钥字符串,按实际版本调整。
"""
if not encoded:
return ""
try:
raw = base64.b64decode(encoded)
except Exception:
return "[Base64解码失败]"
result = bytearray()
key_bytes = key.encode("utf-8")
for i in range(len(raw)):
result.append(raw[i] ^ key_bytes[i % len(key_bytes)])
try:
return result.decode("utf-8")
except UnicodeDecodeError:
return "[解码结果非UTF-8文本,可能密钥不正确]"
def main():
config_path = input("输入 connections.json 的路径: ").strip()
key = input("输入密钥字符串(例如 finalshell): ").strip() or "finalshell"
if not os.path.exists(config_path):
print("文件不存在")
sys.exit(1)
with open(config_path, "r", encoding="utf-8") as f:
data = json.load(f)
# 兼容不同版本的结构:可能是数组,也可能包了一层
if isinstance(data, dict):
connections = data.get("connections", data.get("server_list", []))
else:
connections = data
for conn in connections:
if not isinstance(conn, dict):
continue
name = conn.get("name", "未命名")
host = conn.get("host", "")
port = conn.get("port", 22)
username = conn.get("username", "")
enc_pwd = conn.get("password", "")
pwd = decrypt_password(enc_pwd, key)
print(f"名称: {name}")
print(f"地址: {host}:{port}")
print(f"用户名: {username}")
print(f"明文密码: {pwd}")
print("-" * 40)
if __name__ == "__main__":
main()
把这个脚本保存为 finalshell_decrypt.py,然后运行它,输入 connections.json 的路径,就能逐条打印连接信息和还原出的密码。
3.3 运行脚本,批量导出全部密码
我实际运行一次,输出效果是这样的:
code复制名称: 测试服务器
地址: 192.168.1.100:22
用户名: root
明文密码: MyPassw0rd@123
如果你连接数量很多,直接把输出重定向到文件里更实用:
bash复制python finalshell_decrypt.py > passwords.txt
这样所有连接信息和密码会集中写入一个文本文件,方便对照或二次整理。
这里要多说一句:脚本看起来简单,但密钥匹配是大坑。如果你用了某个网上流传的脚本却一直解不出来,大概率是密钥对不上当前版本。常见处理办法是去网上搜“FinalShell 对应版本 解密 key”,你只需要保证脚本中的密钥与环境匹配,其他逻辑不太会变。
4. 完整实操:一条命令把多个连接密码导出成可读表格
上面那一段是基础流程,但实战中还经常遇到格式不统一、字段缺失、同一条连接切换过认证方式等情况。这一节我再从头到脚走一遍完整实操,目标是让你在自己机器上复制成功。
4.1 环境准备:Windows 下安装 Python
如果你的电脑还没装 Python,需要先装一个。去 Python 官网下载 Windows 安装包,安装时记得勾选“Add Python to PATH”,其他保持默认即可。
安装完成后打开命令提示符验证:
bash复制python --version
能看到版本号就说明正常。之后我会用到 json、base64、os、sys 这些标准库,不需要额外 pip 安装任何第三方包,所以环境准备这一步很简单。
4.2 完整脚本代码与参数说明
我把上一节的脚本稍微增强一下,加入自动定位配置文件目录的功能:
python复制import json
import base64
import os
import sys
from pathlib import Path
def locate_config_file():
"""按常见路径自动查找 connections.json"""
candidates = [
Path(os.environ.get("APPDATA", "")) / "finalshell" / "connections.json",
Path(os.environ.get("LOCALAPPDATA", "")) / "finalshell" / "connections.json",
Path.home() / ".finalshell" / "connections.json",
Path.home() / "Library" / "Application Support" / "finalshell" / "connections.json",
]
for p in candidates:
if p.exists():
return p
return None
def decrypt_password(encoded, key):
if not encoded:
return ""
try:
raw = base64.b64decode(encoded)
except Exception:
return "[非标准Base64]"
key_bytes = key.encode("utf-8")
result = bytearray()
for i in range(len(raw)):
result.append(raw[i] ^ key_bytes[i % len(key_bytes)])
try:
return result.decode("utf-8")
except UnicodeDecodeError:
return "[密钥不匹配]"
def main():
config_path = locate_config_file()
if config_path is None:
print("未找到 connections.json,请手动指定路径")
return
key = "finalshell" # 根据你的版本调整
with open(config_path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict):
connections = data.get("connections", data.get("server_list", []))
else:
connections = data
results = []
for conn in connections:
if not isinstance(conn, dict):
continue
results.append({
"name": conn.get("name", ""),
"host": conn.get("host", ""),
"port": conn.get("port", ""),
"username": conn.get("username", ""),
"password": decrypt_password(conn.get("password", ""), key)
})
# 输出为 CSV 格式,方便用 Excel 打开
with open("finalshell_export.csv", "w", encoding="utf-8-sig", newline="") as f:
f.write("名称,主机,端口,用户名,密码\n")
for r in results:
f.write(f"{r['name']},{r['host']},{r['port']},{r['username']},{r['password']}\n")
print(f"成功导出 {len(results)} 条连接记录到 finalshell_export.csv")
if __name__ == "__main__":
main()
这段脚本在上一节基础上做了三件事:
- 自动搜索常见配置目录,省去手动输入路径。
- 同时解析
connections、server_list两种结构,兼容性更好。 - 把结果输出为带 BOM 的 CSV,直接用 Excel 打开不乱码。
这里编码用了 utf-8-sig,目的就是为了 Excel 打开中文不乱码。很多初写脚本的人容易忽略这个细节,结果导出的 CSV 在 Excel 里中文全变乱码,还要折腾半天。
4.3 结果验证与常见输出
运行脚本后,同一目录下会生成 finalshell_export.csv。你用 Excel 打开,应该看到类似下面的表格:
| 名称 | 主机 | 端口 | 用户名 | 密码 |
|---|---|---|---|---|
| 测试服务器 | 192.168.1.100 | 22 | root | MyPassw0rd@123 |
| 数据库主机 | 10.0.0.5 | 22 | ubuntu | Ubuntu#2024 |
如果密码列出现“[密钥不匹配]”,说明配置文件里的加密方式和脚本里的密钥不一致,需要调整密钥变量后再试。
5. 密码导出这件事,既是便利更是责任
把密码导出来这件事,能做到不代表应该到处做。我接触过不少同行,平时习惯把所有服务器密码都保存在 FinalShell 里,觉得图方便,一旦导出成明文文件,就等于把整套服务器凭据打包送人。这里必须认真聊聊安全问题。
5.1 明文密码文件的安全风险
你导出的 CSV 或者文本文件里的内容是纯明文,任何拿到这个文件的人都可以直接登录你的服务器。这不是危言耸听,我有一次把导出的密码文件放在桌面,后来因为下载了个“破解版软件”导致整机中毒,第一反应就是赶紧换掉所有服务器密码。
一旦密码文件泄露,攻击者不需要费力破解,直接按着清单登录即可。运维人员最怕的事情,不是密码复杂度不够,而是密码被明文集中保存后一次性泄露。
5.2 迁移后记得做这些安全动作
如果你确实需要导出密码做迁移,建议完成后尽快做以下几件事:
- 导出完成后,立即删除临时生成的明文文件,不要放在桌面、下载目录或网盘。
- 如果导出的 CSV 中包含密码,把文件放进加密压缩包,或者用系统自带的 BitLocker、FileVault 加密整个磁盘。
- 迁移完成后先在另一台电脑上测试连接,确认所有密码都能用,再清理旧电脑上的配置缓存和导出文件。
- 如果怀疑文件曾经过第三方之手,宁可花时间重置关键服务器密码,也不要冒险保留。
5.3 长期推荐:用 SSH 密钥登录替代密码
与其不断折腾导出密码,我更推荐你把 FinalShell 里保存的连接改成 SSH 密钥认证方式。
具体来说,你可以在本机生成一对密钥,把公钥放到服务器 ~/.ssh/authorized_keys 里,FinalShell 连接时选择密钥认证,配置里就不需要保存明文密码。即使 connections.json 泄露,对方拿不到私钥也无法登录,风险低很多。
当然,密钥认证也有自己的维护成本,比如私钥丢了就得重新配置,多台服务器的公钥分发也需要一点脚本自动化。但为了服务器安全,这笔投入是值得的。
6. 常见问题与排查技巧实录
结合我自己折腾的几次经历,加上帮群里朋友解决问题的经验,把常见的坑集中整理成一份速查表。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 备份文件导入后连接列表为空 | 备份版本高于导入版本 | 检查新旧版本一致,或升级新电脑 FinalShell |
| 找不到 connections.json | FinalShell 未保存任何连接,或使用了非标准安装目录 | 新建一个测试连接后重新搜索 |
| 解密输出乱码 | 密钥不匹配 | 换对应版本的正确密钥重试 |
| JSON 文件提示解析失败 | 程序异常退出导致文件损坏 | 找备份目录或者之前的备份文件恢复 |
| 密码字段本身为空 | 原本就没有保存密码,只保存了用户名 | 只能手动补填 |
| CSV 打开中文乱码 | 编码不是带 BOM 的 UTF-8 | 用脚本导出时指定 utf-8-sig 而不是 utf-8 |
6.1 备份文件导入后连接列表为空
这个我踩过。旧电脑还是 3.9 版本,新电脑直接装了 4.1,结果导出导入后连接列表一片空白。原因是新版本改动了配置结构,旧版导出文件里的部分字段校验不通过,导入过程被中断。
解决办法是去官网找一个与新版本同大版本的旧版安装包,先把配置导入,再升级到新版。
6.2 目录里找不到 connections.json
如果你刚安装 FinalShell 还没创建任何连接,配置文件不会生成。先在软件里手工新建一条连接并连接一次,确保配置落盘后再去搜索。
Windows 下如果 %APPDATA%\finalshell 里确实没有,可以搜索整个用户目录下的 *.json 文件,再用文本编辑器打开查看,看到类似 "host"、"password" 字样的就是目标文件。
6.3 解密出来是乱码或空字符串
最常见的原因是脚本里的密钥跟实际版本不一致,尤其是一些国内修改版或绿色汉化版。另外,如果连接用了 SSH 密钥而不是密码,password 字段可能为空或存的是空串,解密出来自然是空的。
另一种少见情况是 FinalShell 4.x 之后改用了 AES 类算法,不再是简单的异或。遇到这种你就别纠结在旧脚本上了,去官方渠道升级程序,或者直接用界面导出备份文件,在新环境里恢复。
6.4 配置丢失后如何用导出文件找回
如果你之前的 FinalShell 已经打不开,但目录里还有一个备份文件或者其他残留 json,优先把整个目录复制一份,然后在新电脑上安装对应版本,再执行导入操作。哪怕导入失败,也先留好原始文件,不要反复覆盖。
一个土办法:用 WinHex 或免安装的 Hex 编辑器打开疑似损坏的 json 文件,查看内容是否还能识别出 host 字段。只要还能看到明文 host 信息,就有机会手工重建连接,密码就算解不出来,至少服务器地址、端口、用户名全部能找回,密码重置一遍也就几分钟的事。
我在实际使用中体会最深的一点是:FinalShell 这类工具虽然把“保存密码”做得很方便,但真正可靠的密码管理还是得靠主动备份和密钥认证双保险。每次 migration 都做一次完整配置目录快照,比我临时去翻配置文件、猜密钥版本省心太多。最后再分享一个我最近养成的习惯:每次导出完密码或者配置文件,我都会在 FinalShell 里把连接记录顺手更新一遍,防止某些连接密码已经被我重置过而配置文件里还是旧密码,等到要用时才傻眼。
