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 业界标准,无需额外依赖

架构设计

整个项目分为四层:

UI Layer (Compose Screens + ViewModels)
    ↓
Domain Layer (SettingsManager · CloudSyncApi · CryptoManager)
    ↓
Data Layer (Room DB · ConnectBot Service · SSH Transport)
    ↓
Crypto Layer (SessionKeyHolder → FieldCrypto → AES-256-GCM)

关键决策:去掉登录页

最初版本有主密码登录页,用户需要输入密码才能进入。但经过实际使用发现:

  1. 手机本身有锁屏,App 再加一层密码是多余的安全感
  2. 多端同步时密码管理复杂,需要 salt 同步、密钥派生一致
  3. 用户反馈:每次打开都要输密码太麻烦

最终方案: - 设备密钥自动生成:随机 256-bit 密钥,持久化存储,用户无感知 - 备份密码独立:只在备份/恢复时使用,与设备密钥无关

// 自动生成设备密钥
val deviceKey = ByteArray(32).also { SecureRandom().nextBytes(it) }
cryptoManager.saveDeviceKey(deviceKey)
SessionKeyHolder.set(deviceKey)

密钥体系设计

设备密钥 (自动生成) → 加密数据库字段 (密码/私钥)
备份密码 (用户设置) → 加密备份文件 + GitHub 备份
云同步密码 (服务器账号) → 加密云端数据

三个密钥完全独立,互不影响。这样做的好处:

  • 本地数据自动加密,用户无需操作
  • 备份文件用独立密码保护,跨设备恢复只需密码
  • 云同步用服务器账号密码,天然支持多端

功能实现

1. SSH 终端

保留了 ConnectBot 的终端内核,但 UI 完全重写:

  • 快捷键栏常驻(Ctrl/Esc/Tab/方向键/功能键)
  • Compose 实现,支持深色模式
  • 光标自动避让手机键盘

2. SFTP 文件管理

用 JSch 的 ChannelSftp 直接实现,不依赖 ConnectBot 的旧代码:

  • 远程目录浏览(面包屑导航)
  • 上传/下载(进度条 + 取消)
  • 删除/重命名/权限修改
  • Android 10+ 分区存储兼容

3. 加密备份

备份文件格式(BackupEnvelope):

{
  "format": "boshconnect_backup_v1",
  "encrypted": true,
  "payload": "AES-GCM 加密的 JSON",
  "salt": "base64 编码的 PBKDF2 salt",
  "createdAt": 1743638400000
}

关键设计:备份文件内嵌 salt。这样任何设备只要有备份密码,就能派生相同的密钥来解密。

4. GitHub 备份

原理很简单:

// 备份
PUT https://api.github.com/repos/{owner}/{repo}/contents/boshconnect/backup_20260403.enc
Authorization: token {PAT}
Body: { message, content: base64(encrypted_data) }

// 恢复
GET https://api.github.com/repos/{owner}/{repo}/contents/boshconnect/
→ 下载最新文件 → 输入备份密码 → 解密 → 写入本地

优势:零服务器维护、GitHub 免费私有仓库、天然版本控制、安全性高。

5. 云同步(SbSSH Server)

自建 FastAPI 服务端,端到端加密:注册/登录 → JWT 认证 → 数据加密后上传,服务端只存密文。

踩坑记录

1. Kotlin 版本兼容

ConnectBot 的 termlib 库编译用了 Kotlin 2.3.0,但项目用的是 2.0.21。直接导致 metadata 版本不匹配。最终回退到 2.0.21,在 termlib 依赖上排除 Kotlin stdlib。

2. Google Maven TLS 握手失败

服务器通过代理访问 dl.google.com 时 TLS 握手失败。解决:所有 build.gradle 加阿里云 Maven 镜像。

3. 多端同步密钥不一致

早期版本每个设备生成自己的 salt,导致不同设备派生出不同的密钥。解决:登录时从服务器获取 salt,用服务器的 salt 派生密钥。

4. Android 14 图标显示问题

Adaptive Icon 的 XML 矢量图和 PNG 混合使用导致异常。最终用 Python PIL 生成 PNG,通过 layer-list drawable 包装。

总结

这个项目的核心价值:复用而不是重写(SSH 内核用 ConnectBot)+ 现代技术栈(Compose + Material 3)+ 安全优先(字段级加密、独立备份密码)+ 用户友好(去掉登录页、GitHub 备份零配置)。


本文由博客助手大龙虾整理。