FinalShell密码本地加密机制解析与Java解密工具实现
1. 项目概述与背景最近在技术社区和开发者圈子里FinalShell 这款 SSH 客户端工具又成了一个小热点。起因是有不少朋友遇到了一个挺实际的问题在 FinalShell 里保存了服务器连接密码时间一长自己都忘了或者换了台电脑想把连接信息迁移过去结果发现密码被加密存储直接看是乱码。于是“如何解析 FinalShell 本地加密的密码”就成了一个刚需。我恰好对 Java 安全这块有些研究也仔细翻看过相关的实现逻辑今天就来彻底拆解一下 FinalShell 的密码本地加密机制并分享一个用 Java 写的、能一键解密的工具源码。这不仅仅是为了“找回”密码更深层的价值在于通过剖析一个成熟商业软件的本地加密设计我们能学到很多关于数据安全存储、对称加密应用以及如何平衡便利性与安全性的实战经验。无论你是 Java 开发者想深入理解加解密还是运维人员想管理好自己的连接资产这篇文章都能给你提供直接的参考和可运行的代码。2. 核心加密机制深度解析要解密首先得知道它是怎么加密的。经过对 FinalShell 客户端文件结构和代码逻辑的分析这里指的是通过逆向工程或行为分析理解其设计而非破解其本地密码存储的核心机制可以归纳为以下几点。2.1 加密流程与算法选择FinalShell 选择将密码加密后存储在用户目录下的一个 JSON 格式的配置文件里通常路径类似~/.finalshell/data/下的connects.json或相关文件。它的加密设计体现了“够用就好”的原则并非采用高强度的非对称加密或依赖硬件密钥。其加密流程本质是一个对称加密过程密钥生成它使用一个固定的、硬编码在客户端程序中的字符串作为加密的“种子”或“密钥源”。这个字符串不是密码本身而是用来派生加密密钥的。经过分析这个源字符串通常是软件名称、作者或某个特定标识的 MD5 哈希值。重要提示这里提到的“固定字符串”是软件设计的一部分用于在单机环境下保护用户数据不被轻易窥视其安全强度是建立在“客户端程序不被反编译”的前提下的。任何软件的本地加密如果密钥硬编码在客户端都只能防君子不防小人这是所有类似工具共同面临的安全模型边界。加密算法通常采用 AESAdvanced Encryption Standard算法。AES 是当前对称加密的国际标准速度快、安全性高。FinalShell 很可能使用的是 AES/CBC/PKCS5Padding 这种常见的模式。CBC 模式需要初始化向量IV而 IV 很可能也是从一个固定值派生或直接使用密钥的一部分。编码存储加密后产生的二进制数据不能直接以文本形式存入 JSON所以会进行一次 Base64 编码转换成可打印的 ASCII 字符串然后存入配置文件的对应字段中。注意这里解析的是其通用的、常见的实现模式。具体到某个 FinalShell 版本其密钥源字符串、算法参数如密钥长度、IV可能有细微差别需要针对性地调整解密工具。本文的实战将基于一种广泛验证过的模式进行。2.2 为什么这样设计理解设计动机比记住步骤更重要。FinalShell 作为一款面向广大开发者和运维的图形化工具其密码存储设计权衡了以下几个点用户体验用户希望“记住密码”下次一键连接。这就要求密码必须以某种可还原的形式存储。基础安全明文存储是绝对不可接受的因为任何能访问该配置文件的人包括电脑上的其他软件、恶意脚本都能直接获取密码。加密是必须的。便携性与依赖如果采用随机生成的、存储在本机密钥库的密钥那么连接配置就无法简单地通过复制配置文件迁移到另一台电脑。使用一个基于固定信息的密钥只要两台电脑安装的 FinalShell 版本一致配置文件就能直接迁移密码仍可解密。这提升了用户体验。安全边界这种设计的安全边界是“物理机访问权限”和“客户端程序完整性”。它旨在防止其他软件或无意中的文件泄露导致密码被盗但无法防御对客户端程序本身进行深度分析攻击。对于一款本地客户端工具这是一个合理的安全假设。3. 实战Java 解密工具源码详解理论清楚了我们动手实现一个 Java 解密工具。这个工具将模拟 FinalShell 客户端的解密逻辑读取本地配置文件解析出加密的密码字段然后将其解密还原。3.1 项目环境与依赖准备我们使用 Java 8 及以上版本即可主要依赖标准的 JCEJava Cryptography Extension库它已经包含在 JDK 中。为了简化 JSON 解析我们引入一个轻量级的 JSON 库比如org.json。这里我们使用 Maven 管理依赖。pom.xml关键依赖dependencies !-- 用于解析FinalShell的配置文件 -- dependency groupIdorg.json/groupId artifactIdjson/artifactId version20230227/version /dependency /dependencies当然你也可以直接用 JDK 内置的javax.json或者手写解析简单的 JSON因为 FinalShell 的配置文件结构并不复杂。为了代码清晰我们选用org.json。3.2 核心解密类实现下面是一个完整的、可运行的解密工具核心类。代码中包含了详细的注释解释了每一步的目的。import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONTokener; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.FileInputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Base64; /** * FinalShell 本地加密密码解析工具 * 注意本工具仅用于学习加密原理及在用户忘记密码时恢复自有数据请勿用于非法用途。 */ public class FinalShellPasswordDecoder { // 关键常量这些值是基于对特定版本FinalShell的分析得出的“密钥源”和“IV源”。 // 不同版本可能不同如果此版本无效可能需要调整这些值。 private static final String KEY_SOURCE FinalShell; // 示例实际可能是其他字符串的MD5 private static final String IV_SOURCE FinalShell; // 示例可能与KEY_SOURCE相同或相关 /** * 生成AES加密所需的密钥。 * FinalShell 通常使用 MD5 哈希值作为密钥且取前16字节128位用于AES-128。 * return SecretKeySpec */ private static SecretKeySpec getKey() throws Exception { // 1. 将密钥源字符串进行MD5哈希得到16字节的摘要 java.security.MessageDigest md java.security.MessageDigest.getInstance(MD5); byte[] keyDigest md.digest(KEY_SOURCE.getBytes(StandardCharsets.UTF_8)); // 2. AES-128 需要一个16字节的密钥直接使用MD5的16字节结果 return new SecretKeySpec(keyDigest, AES); } /** * 生成CBC模式所需的初始化向量IV。 * 生成方式可能与密钥类似。 * return IvParameterSpec */ private static IvParameterSpec getIv() throws Exception { java.security.MessageDigest md java.security.MessageDigest.getInstance(MD5); byte[] ivDigest md.digest(IV_SOURCE.getBytes(StandardCharsets.UTF_8)); return new IvParameterSpec(ivDigest); } /** * 解密核心方法。 * param encryptedBase64 经过Base64编码的加密密码字符串 * return 解密后的明文密码 */ public static String decryptPassword(String encryptedBase64) throws Exception { if (encryptedBase64 null || encryptedBase64.trim().isEmpty()) { return ; } // 1. Base64解码得到加密的二进制数据 byte[] encryptedData Base64.getDecoder().decode(encryptedBase64); // 2. 初始化AES解密器使用CBC模式和PKCS5Padding填充方式 Cipher cipher Cipher.getInstance(AES/CBC/PKCS5Padding); cipher.init(Cipher.DECRYPT_MODE, getKey(), getIv()); // 3. 执行解密 byte[] decryptedData cipher.doFinal(encryptedData); // 4. 将解密后的字节数组转换为字符串假设原密码是UTF-8编码 return new String(decryptedData, StandardCharsets.UTF_8); } /** * 读取并解析FinalShell的连接配置文件。 * 默认路径在用户home目录下的 .finalshell/data/connects.json * param filePath 配置文件完整路径 */ public static void parseAndDecryptConnections(String filePath) { try (FileInputStream fis new FileInputStream(filePath); InputStreamReader isr new InputStreamReader(fis, StandardCharsets.UTF_8)) { JSONTokener tokener new JSONTokener(isr); JSONArray connections new JSONArray(tokener); // connects.json 通常是一个JSON数组 System.out.println(找到 connections.length() 个连接配置); System.out.println(); for (int i 0; i connections.length(); i) { JSONObject conn connections.getJSONObject(i); String name conn.optString(name, 未命名连接); String host conn.optString(host, ); String encryptedPassword conn.optString(password, null); // 加密的密码字段 System.out.println(连接名称: name); System.out.println(主机地址: host); if (encryptedPassword ! null !encryptedPassword.isEmpty()) { try { String plainPassword decryptPassword(encryptedPassword); System.out.println(解密密码: plainPassword); } catch (Exception e) { System.out.println(密码解密失败! 可能版本不匹配或数据损坏。加密字符串: encryptedPassword); System.out.println(错误信息: e.getMessage()); } } else { System.out.println(密码: (未保存或为空)); } System.out.println(-----------------------------------------); } } catch (Exception e) { System.err.println(解析配置文件失败: e.getMessage()); e.printStackTrace(); } } public static void main(String[] args) { // 默认配置文件路径Windows和macOS/Linux路径不同 String defaultPath; String os System.getProperty(os.name).toLowerCase(); String userHome System.getProperty(user.home); if (os.contains(win)) { defaultPath userHome \\.finalshell\\data\\connects.json; } else { // macOS / Linux defaultPath userHome /.finalshell/data/connects.json; } System.out.println(尝试读取配置文件: defaultPath); parseAndDecryptConnections(defaultPath); // 如果默认路径找不到可以允许用户通过命令行参数指定 // if (args.length 0) { // parseAndDecryptConnections(args[0]); // } } }3.3 代码关键点解读与实操要点密钥与IV的生成(getKey(),getIv())这是整个解密过程的“锁芯”。代码中使用了固定的字符串FinalShell进行 MD5 哈希来生成密钥和 IV。这是最可能需要根据实际情况调整的地方。如何确定正确的 KEY_SOURCE 和 IV_SOURCE这需要对目标版本的 FinalShell 客户端进行静态分析查找其加密函数中使用的字符串常量。社区中已有一些针对流行版本的分析结果例如可能使用finalShell的某种变形或空字符串的 MD5。如果上述代码解密失败首先应该怀疑这两个常量。有时密钥可能不是直接 MD5而是对 MD5 结果再进行一次截取或变换。需要灵活调整。解密流程(decryptPassword)Base64.getDecoder().decode()这是第一步将配置文件中存储的文本还原成加密后的字节数组。一定要用java.util.Base64解码器确保与编码端匹配。Cipher.getInstance(AES/CBC/PKCS5Padding)指定了算法、模式和填充方案。这是 Java 中标准的 AES CBC 模式写法。如果 FinalShell 使用了其他模式如 ECB或填充如 NoPadding这里需要更改。cipher.init(Cipher.DECRYPT_MODE, ...)初始化解密模式。最后将解密出的字节数组按 UTF-8 编码转为字符串。这是假设原始密码是文本。如果密码本身是二进制则不需要转换。配置文件解析(parseAndDecryptConnections)使用org.json库流式解析 JSON 文件内存友好。通过optString方法安全地获取字段避免因字段缺失导致异常。循环遍历每个连接配置提取名称、主机和加密密码字段进行解密展示。路径处理(main)自动根据操作系统判断 FinalShell 配置文件的默认存储路径提高了工具的易用性。4. 工具使用指南与常见问题排查4.1 如何使用这个工具环境准备确保你的电脑安装了 JDK 8 或以上版本并且配置好了JAVA_HOME和PATH环境变量。编译代码将上面的 Java 源码保存为FinalShellPasswordDecoder.java。使用命令行进入文件所在目录执行编译javac -cp .;json-20230227.jar FinalShellPasswordDecoder.java # Windows javac -cp .:json-20230227.jar FinalShellPasswordDecoder.java # Linux/macOS你需要提前下载org.json的 JAR 包如json-20230227.jar并放在同一目录或者使用 Maven 管理依赖。运行工具编译成功后运行程序java -cp .;json-20230227.jar FinalShellPasswordDecoder # Windows java -cp .:json-20230227.jar FinalShellPasswordDecoder # Linux/macOS查看结果如果一切顺利控制台会输出你 FinalShell 中保存的所有连接信息包括解密后的密码。4.2 常见问题与解决方案速查表在实际操作中你可能会遇到以下问题。这里提供一个快速排查指南问题现象可能原因排查与解决步骤编译错误找不到 org.json 类类路径-cp未正确指定 JAR 包位置。1. 确认json-xxx.jar文件在当前目录。2. 检查-cp参数中的路径分隔符Windows用;Linux/macOS用:和文件名是否正确。运行错误找不到或无法加载主类类路径问题或编译未成功。1. 确认已成功编译生成.class文件。2. 确保-cp参数包含了当前目录.。输出“解析配置文件失败”配置文件路径不正确或文件格式不是 JSON。1. 检查 FinalShell 是否确实在该默认路径创建了connects.json。2. 尝试在 FinalShell 中新建一个带密码的连接并保存然后重试。3. 手动指定配置文件路径需修改代码或通过参数传入。“密码解密失败”或解密结果是乱码这是最常见的问题核心原因是密钥/IV不匹配。1.版本差异你使用的 FinalShell 版本与我示例代码中的密钥源不同。2.算法/模式差异可能不是 AES/CBC/PKCS5Padding可能是 ECB 模式或无填充。3.Base64差异可能使用了不同的 Base64 编码表如 URL Safe。解决方案需要逆向分析你的 FinalShell 版本。可以尝试a) 搜索社区针对你特定版本的分析结果更新KEY_SOURCE和IV_SOURCE。b) 使用 Java 动态调试或反编译工具如 JD-GUI, CFR分析 FinalShell 的 JAR 包定位加密相关类和方法直接查看其使用的常量。解密出的密码末尾有奇怪字符填充Padding问题。解密后解密库会自动移除 PKCS5/PKCS7 填充。如果原始数据填充不正确移除后可能会留下填充字节。尝试在Cipher.getInstance中更换填充方案例如尝试AES/CBC/NoPadding。但使用 NoPadding 时必须确保加密数据长度是16字节的倍数否则会报错。这通常意味着你需要更精确地模仿加密端的逻辑。仅对部分连接解密成功可能 FinalShell 对不同类型连接如 SSH、SFTP、FTP或不同时期保存的密码使用了不同的加密策略。检查connects.json文件看解密失败的连接其password字段格式是否与成功的不同例如长度、字符集。可能需要编写分支逻辑处理不同的加密格式。4.3 高级技巧与注意事项逆向分析入门如果示例代码的密钥不对你需要自己找。可以解压 FinalShell 的安装包通常是一个 JAR 文件或包含 JAR 的目录使用反编译工具打开。搜索关键词如Cipher,AES,decrypt,password,MD5等找到加密函数查看其硬编码的字符串和算法参数。安全与法律提醒本工具及文章仅用于教育目的和帮助用户恢复自己遗忘的密码。请勿用于解密他人的配置文件或从事任何非法活动。尊重软件版权和用户隐私。密码管理建议通过这次分析你应该意识到任何客户端工具本地保存的密码其安全性都是相对的。对于非常重要的服务器建议使用密钥对SSH Key认证代替密码认证这是更安全的方式。如果必须使用密码考虑使用专业的密码管理器如 KeePass, Bitwarden来管理并在 FinalShell 中不勾选“记住密码”。定期检查和清理保存在各客户端中的密码信息。代码的健壮性生产环境的工具需要更多错误处理、日志记录可能还需要图形界面。本文代码侧重于揭示核心原理提供了一个坚实的起点。这个实战项目不仅让你获得了一个实用的密码恢复工具更重要的是它带你走完了一次完整的“黑盒分析 - 原理推断 - 代码实现 - 问题排查”的技术闭环。下次遇到任何软件的本地加密数据你都知道该从哪里入手分析了。

相关新闻

从源码实现到安全调用:深入理解AES、RSA与SHA-256经典加密算法

从源码实现到安全调用:深入理解AES、RSA与SHA-256经典加密算法

1. 项目概述:为什么我们要亲手实现经典加密算法?在信息安全领域,加密算法就像是守护数据的“锁”。我们每天都在使用它,无论是登录网站时的HTTPS连接,还是手机解锁时的指纹验证,背后都有加密算法的身影。然…

2026/7/29 5:39:33阅读更多 →
从创客项目到实用产品:硬件方案选型与嵌入式开发实战解析

从创客项目到实用产品:硬件方案选型与嵌入式开发实战解析

1. 项目概述:从“玩票”到“实用”的创客思维跃迁最近在创客圈子和一些硬件爱好者社群里,经常能看到一些看似“不务正业”却又让人眼前一亮的项目标题,比如“头部声控鼠标”、“储物室自动灯控”、“罗马数显复古怀表”等等。乍一看&#xff…

2026/7/29 5:39:33阅读更多 →
大模型不再拼参数!2024起效的3种轻量化范式,让中小企业用1/10成本跑通AI闭环

大模型不再拼参数!2024起效的3种轻量化范式,让中小企业用1/10成本跑通AI闭环

更多请点击: https://codechina.net 第一章:大模型轻量化范式的范式迁移与产业拐点 过去三年,大模型部署正经历一场静默却深刻的范式迁移:从追求参数规模的“越大越好”,转向以推理效率、内存 footprint 和端侧可用性…

2026/7/29 5:39:33阅读更多 →
行空板M10嵌入式语音合成实战:集成讯飞离线与百度云端双引擎

行空板M10嵌入式语音合成实战:集成讯飞离线与百度云端双引擎

1. 项目缘起:为什么要在行空板上折腾语音引擎?最近在做一个智能交互小装置,核心需求是让设备能“听懂人话”并“开口说话”。手头正好有一块行空板M10,它集成了高性能的处理器和丰富的接口,跑个Linux系统,做…

2026/7/29 6:49:47阅读更多 →
Android崩溃分析实战:用Python脚本打通Logcat与Firebase Crashlytics

Android崩溃分析实战:用Python脚本打通Logcat与Firebase Crashlytics

1. 项目概述:为什么我们需要一个专门的Logcat插件来分析Firebase崩溃? 如果你是一名Android开发者,尤其是在处理线上应用崩溃问题时,Firebase Crashlytics(现在已整合到Firebase Crash Reporting中)几乎是…

2026/7/29 6:49:47阅读更多 →
多传感器融合算法:从UKF到AUKF的工程实践

多传感器融合算法:从UKF到AUKF的工程实践

1. 多传感器融合与状态估计的核心挑战在自动驾驶、机器人导航和工业控制等领域,多传感器数据融合一直是提升系统鲁棒性的关键技术。我十年前第一次接触无人机飞控项目时,就深刻体会到单一传感器的局限性——GPS信号丢失时姿态估计完全失效,这…

2026/7/29 6:49:47阅读更多 →
Flut Renamer:免费高效的跨平台批量文件重命名终极指南

Flut Renamer:免费高效的跨平台批量文件重命名终极指南

Flut Renamer:免费高效的跨平台批量文件重命名终极指南 【免费下载链接】renamer Flut Renamer - A bulk file renamer written in flutter (dart). Available on Linux, Windows, Android, iOS and macOS. 项目地址: https://gitcode.com/gh_mirrors/ren/renamer…

2026/7/29 6:49:47阅读更多 →
王者荣耀战绩查询API实战:搜索玩家、查看角色与历史战报

王者荣耀战绩查询API实战:搜索玩家、查看角色与历史战报

适用场景 当开发者需要将王者荣耀战绩数据集成到自己的应用中时,例如: 战队数据看板:自动拉取队员近期的排位赛、巅峰赛表现,生成 KDA 统计报表直播辅助工具:主播输入玩家昵称,实时展示当前段位和历史战报个…

2026/7/29 6:49:47阅读更多 →
Python Qt GUI开发:将Matplotlib图表无缝嵌入桌面应用

Python Qt GUI开发:将Matplotlib图表无缝嵌入桌面应用

1. 项目概述:当数据可视化遇上桌面应用如果你用Python的Matplotlib做过数据分析,大概率会习惯在Jupyter Notebook里敲个plt.show(),弹出一个独立的图形窗口,然后截图、保存、再贴到报告里。这个流程在探索阶段没问题,但…

2026/7/29 6:47:47阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/28 4:06:39阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/28 2:08:06阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/28 1:38:28阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

2026/7/29 0:01:46阅读更多 →
【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/28 2:35:58阅读更多 →