加密体系:TLS + XChaCha20 双层 + Ed25519 签名
三层加密叠在一起
PlainApp 从浏览器到手机有三层加密,层层加码:
┌──────────────────────────────────────────┐
│ 浏览器 / CLI │
│ │
│ Layer 1: TLS (HTTPS) │ ← 传输层基础加密 + 完整性保护
│ Layer 2: XChaCha20-Poly1305 (应用层) │ ← TLS 之上再加一层零信任加密
│ Layer 3: Ed25519(仅 P2P 聊天 / PlainCast)│ ← 设备身份签名
└──────────────────────────────────────────┘
Layer 1: TLS(HTTPS)
这是最基础的一层。PlainApp 手机端启动时会自己生成一个自签名 TLS 证书,然后用它跑 Web Server。
自签名证书是什么意思:
- 证书是手机自己签的,不是 DigiCert / Let's Encrypt 这类权威 CA 签的
- 浏览器无法验证"这个证书确实是手机发的"——因为没有 CA 给它背书
- 所以浏览器显示"连接不安全 / 你的连接不是私密连接"警告
怎么处理这个警告
不是 bug,是正常行为。三种方式:
- 点"高级"→"继续前往" — 最常用,Chrome/Firefox/Safari/Brave 都支持这个流程
- 在浏览器里导入自签名证书到信任列表 — 一劳永逸,但每个浏览器都要操作
- 用 http://(8080 端口)代替 https://(8443 端口) — 不推荐,TLS 层没了,只剩 Layer 2 的 XChaCha20
TLS 用的加密套件
PlainApp 手机端 TLS Server 支持的套件(根据官方文档和 GitHub 源码):
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
都是现代安全的套件。
Layer 2: XChaCha20-Poly1305(应用层)
TLS 之上 PlainApp 再加了一层自己的加密——不管 TLS 有没有、是否被中间人攻击,应用层数据本身就是加密的。
为什么要加这一层
TLS 依赖证书验证。如果你在浏览器里"点了高级→继续前往",就跳过了证书验证——这时候一个同一 Wi-Fi 上的恶意设备可以 MITM 攻击你的 TLS 连接。
PlainApp 加的 XChaCha20-Poly1305 应用层加密不依赖 TLS 证书验证——就算 TLS 被中间人攻破,攻击者拿到的也是一层密文,解不开。
具体怎么做
每次浏览器和 PlainApp 手机端建立新会话时:
- 手机端生成一个临时的 ChaCha20 密钥(会话级、不持久化)
- 这个密钥通过 TLS 的 Diffie-Hellman 握手安全交换到浏览器
- 之后浏览器和手机之间的每一个 HTTP 请求 / 响应体,都在明文基础上用 XChaCha20-Poly1305 加密 + 加认证标签
- 每次通信后这个密钥会被丢弃(下次会话重新生成)
Layer 3: Ed25519(仅 P2P 聊天 / PlainCast)
P2P 聊天和 PlainCast 有额外的身份验证需求——因为需要"配对"(第一次见面的设备),不能只靠"知道地址"就进。
Ed25519 做了什么
每台 PlainApp 设备有一对持久的 Ed25519 公私钥(存在手机本地,不对外):
- 公钥在配对时广播给对方("我是设备 A,这是我的公钥")
- 私钥永不离开手机
- 每条 P2P 聊天消息、每个 PlainCast 视频帧,都会用私钥签一个数字签名
- 接收方用发送方的公钥验签——如果签名对不上,说明消息在传输中被篡改,直接丢弃
自签名证书警告到底安不安全
安全,但需要理解为什么:
| 担忧 | 实际情况 |
|---|---|
| "浏览器说连接不安全就是真不安全" | 浏览器警告的是"无法验证服务器身份",不是"连接被窃听" |
| "同一 Wi-Fi 有人能抓包看明文" | 就算抓包也是 TLS 加密 + XChaCha20 双重加密,解不开 |
| "中间人攻击" | 自签名证书 + 手动信任的方式确实容易被中间人利用——但应用层的 XChaCha20 加密补了这个缺口 |
| "比正规 CA 证书的 Web 应用差吗" | 对于 Local First 场景来说,其实自签名证书反而够用——因为你每次访问都是同一台已知设备的地址,知道"是谁"发的 |
如果还是不安心,你可以手动把 PlainApp 自签名证书导入浏览器信任列表——之后再访问就不会有警告了。
加密的边界(重要)
加密保护的是传输过程。以下内容 不加密 存储:
- 笔记正文(明文存在手机 SQLite 数据库里)
- 文件本身(存在手机文件系统,明文)
- RSS 缓存条目(明文)
- APK 文件(明文)
- 标签、书签、番茄钟历史等(明文)
加密只发生在浏览器 ↔ 手机通信的时候。手机上的存储层目前没有应用级加密——手机本身用 Android Disk Encryption(设置 → 安全 → 加密手机)覆盖的话,整体已经很安全。
常见问题
| 问题 | 处理 |
|---|---|
| 浏览器警告一直去不掉 | 手动导入证书到信任存储;或接受"继续前往"(不影响安全) |
| http:// 和 https:// 该用哪个 | 优先 https://(8443 端口);http://(8080 端口)少了 TLS 层只剩应用层加密 |
| 公网访问怎么加安全 | 不要直接端口转发。用 Tailscale / WireGuard 建立 VPN 后访问 |
| 加密开销大吗 | ChaCha20 在现代手机 CPU 上是硬级指令集加速,几乎无感知 |