BoshConnect:从 ConnectBot 到全功能 SSH/SFTP 客户端的开发实战
基于开源项目 ConnectBot,用 Kotlin + Jetpack Compose 从零打造一款支持加密备份、云同步、GitHub 备份的 Android SSH/SFTP 客户端。本文记录整个开发过程中的架构设计、踩坑经验和技术决策。
为什么要做这个项目
作为一个经常管理多台 VPS 的开发者,我需要一个好用的 Android SSH 客户端。现有的方案各有问题:
- Termius:功能强大但收费,免费版限制多
- JuiceSSH:界面陈旧,Compose 时代的产品长那样说不过去
- ConnectBot:开源老牌,但 UI 还停留在 Android 5 时代
于是我决定:基于 ConnectBot 的 SSH 内核,用现代 Android 技术栈重新打造一个。
项目取名 BoshConnect,开源在 GitHub。
技术栈选型
| 层级 | 选择 | 理由 |
|---|---|---|
| 语言 | Kotlin | Android 官方推荐,类型安全 |
| UI | Jetpack Compose + Material 3 | 声明式 UI,开发效率高 |
| 架构 | MVVM + StateFlow | Google 推荐架构,响应式 |
| DI | Hilt | 标准 Android DI,与 Compose 集成好 |
| 数据库 | Room | 类型安全的 SQLite 封装 |
| SSH | ConnectBot sshlib | 成熟稳定的 SSH 实现 |
| SFTP | JSch | 老牌 Java SSH 库 |
| 加密 | PBKDF2 + AES-256-GCM | 业界标准,无需额外依赖 |
架构设计
整个项目分为四层:
1 | |
关键决策:去掉登录页
最初版本有主密码登录页,用户需要输入密码才能进入。但经过实际使用发现:
- 手机本身有锁屏,App 再加一层密码是多余的安全感
- 多端同步时密码管理复杂,需要 salt 同步、密钥派生一致
- 用户反馈:每次打开都要输密码太麻烦
最终方案:
- 设备密钥自动生成:随机 256-bit 密钥,持久化存储,用户无感知
- 备份密码独立:只在备份/恢复时使用,与设备密钥无关
1 | |
密钥体系设计
1 | |
三个密钥完全独立,互不影响。这样做的好处:
- 本地数据自动加密,用户无需操作
- 备份文件用独立密码保护,跨设备恢复只需密码
- 云同步用服务器账号密码,天然支持多端
功能实现
1. SSH 终端
保留了 ConnectBot 的终端内核,但 UI 完全重写:
- 快捷键栏常驻(Ctrl/Esc/Tab/方向键/功能键)
- Compose 实现,支持深色模式
- 光标自动避让手机键盘
2. SFTP 文件管理
用 JSch 的 ChannelSftp 直接实现,不依赖 ConnectBot 的旧代码:
- 远程目录浏览(面包屑导航)
- 上传/下载(进度条 + 取消)
- 删除/重命名/权限修改
- Android 10+ 分区存储兼容
3. 加密备份
备份文件格式(BackupEnvelope):
1 | |
关键设计:备份文件内嵌 salt。这样任何设备只要有备份密码,就能派生相同的密钥来解密。
4. GitHub 备份
这是最让我满意的功能。原理很简单:
1 | |
优势:
- 零服务器维护
- GitHub 免费私有仓库
- 天然版本控制(每次备份都是一个 commit)
- 安全性高
5. 云同步(SbSSH Server)
自建 FastAPI 服务端,端到端加密:
- 注册/登录 → JWT 认证
- 数据加密后上传,服务端只存密文
- Smart Sync:增量合并,支持保留或删除云端多余数据
踩坑记录
1. Kotlin 版本兼容
ConnectBot 的 termlib 库编译用了 Kotlin 2.3.0,但项目用的是 2.0.21。直接导致:
1 | |
解决:升级 Kotlin 到 2.3.0,但又遇到 kotlinOptions DSL 被废弃的问题,需要迁移到 compilerOptions。最终回退到 2.0.21,在 termlib 依赖上排除 Kotlin stdlib。
2. Google Maven TLS 握手失败
服务器通过代理访问 dl.google.com 时 TLS 握手失败。代理不支持 TLSv1.2/1.3。
解决:所有 build.gradle 加阿里云 Maven 镜像。但 npx cap sync 会覆盖 capacitor-cordova-android-plugins/build.gradle,每次 sync 后要重新加。
3. 多端同步密钥不一致
早期版本,每个设备生成自己的 salt,导致不同设备派生出不同的密钥。设备 A 加密的数据,设备 B 解不开。
解决:登录时从服务器获取 salt(GET /api/v1/sync/salt),用服务器的 salt 派生密钥。所有设备用同一个 salt → 同一个密钥。
4. Android 14 图标显示问题
Android 8+ 需要 Adaptive Icon,但 XML 矢量图和 PNG 的混合使用导致图标显示异常。
最终方案:用 Python PIL 生成 PNG 图标,通过 layer-list drawable 包装,作为 Adaptive Icon 的 foreground。
版本规划
| 版本 | 内容 | 状态 |
|---|---|---|
| v1.0.0 | 基础 SSH/SFTP + 加密 + 云同步 | ✅ 已发布 |
| v1.1.0 | 独立备份密码 + GitHub 备份 + 去掉登录 | ✅ 已发布 |
总结
这个项目的核心价值在于:
- 复用而不是重写:SSH 内核用 ConnectBot,省去了大量协议层工作
- 现代技术栈:Compose + Material 3 让 UI 开发效率提升 10 倍
- 安全优先:字段级加密、独立备份密码、端到端加密同步
- 用户友好:去掉登录页、GitHub 备份零配置
如果你也想自己管理 SSH 密钥,不想把数据交给第三方,可以试试 BoshConnect。
本文由博客助手大龙虾整理。