端到端加密聊天:XChaCha20 + Ed25519 无服务器消息
这不是 WhatsApp 或 Telegram
PlainApp 的聊天不是"又一个聊天 App"——它没有中心服务器:
- 不存你的通讯录在任何云端
- 不记录任何对话历史到任何第三方
- 不要求手机号或邮箱注册
- 消息直接从设备 A 到设备 B(局域网内),经手机的 PlainApp 数据库中转,手机在浏览器端只做 UI
入口位置
侧栏点 Chat。两栏布局:
- 左栏:已配对设备 + 频道列表
- 右栏:当前选中对话的消息历史(加密横幅常驻顶部,告诉你连接是安全的)

加密细节
PlainApp 聊天的加密体系是两层:
传输层:TLS + XChaCha20-Poly1305
所有从手机到浏览器的通信先过 TLS(PlainApp 本地自签名证书),再在 TLS 之上额外加一层 XChaCha20-Poly1305 应用层加密。
消息层:XChaCha20-Poly1305 + Ed25519
每条聊天消息本身还经过一层独立的端到端加密:
- 加密:XChaCha20-Poly1305(ChaCha20 流密码 + Poly1305 认证标签,单次使用密钥)
- 签名:Ed25519 公钥签名(接收方用发送方的公钥验签,防止中间人篡改)
- 密钥存储位置:只在手机端——浏览器端从未接触过 peer 的加密密钥,只负责显示已解密的明文
频道的额外处理
加入多人频道时:每条消息单独为每个成员加密一次(不是"一个密钥、全部解密"的组密钥模型)。这意味着成员 A 离开频道后,A 之前的加密密钥立刻作废——新成员加入后看不到加入之前的任何历史消息。
配对流程(第一次和某设备聊天)
- 手机 A(你的)和手机 B(对方)同时打开 PlainApp 聊天面板
- 两台设备都能扫描到局域网内未配对的 PlainApp 设备(通过 mDNS 广播)
- 在 A 的聊天面板左栏会看到 B 出现在"未配对"区域
- 点一下 B → A 和 B 都会弹出"是否批准配对"对话框
- 双方都点允许 → 交换 Ed25519 公钥 → 配对完成
之后两台设备在同一局域网,对方设备就会出现在左栏"已配对"区域,直接点就能开聊。
核心功能
- 文本消息 — Enter 发送、Shift+Enter 换行
- 文件附件 — 点📎选电脑文件;文件先在手机上加密再发给对端;对方收到后自动解密保存
- 频道邀请 — 可以创建多人频道、分享邀请链接、接受 / 拒绝他人的频道邀请
- 长消息自动转文件 — 超过 2048 字符的消息自动包装成加密文本文件发送,不用你手动处理
- 消息状态 — 每条消息下方显示 pending / sent / delivered;失败的消息点一下可以重试
和 Signal / Matrix 对比
| 维度 | PlainApp Chat | Signal | Matrix |
|---|---|---|---|
| 中心服务器 | 无(局域网直连) | 有(Signal 自有服务器) | 有(Matrix 房间服务器) |
| 端到端加密 | ✅ XChaCha20-Poly1305 + Ed25519 | ✅ Signal Protocol | ✅ Olm/Megolm |
| 跨网络(非局域网) | ❌ 不行 | ✅ | ✅ |
| 手机号注册 | ❌ 不需要 | ✅ 需要 | ❌ 可选 |
| 消息历史跨设备 | ❌ 只在本机手机 | ✅ 绑定账号 | ✅ 服务器同步 |
| 音频 / 视频通话 | ❌ 不支持 | ✅ | ✅ |
PlainApp Chat 的定位是在同一 Wi-Fi 的设备间做快速、安全、无需注册的本地通信,而不是替代 Signal/Matrix 的全球通信。如果你想在出差时用 PlainApp Chat 连家里的手机,可以用 Tailscale 或 WireGuard 把两台设备拉到同一个虚拟局域网里。
浏览器端的角色
重要澄清:PlainApp Chat 不是一个"浏览器端完整聊天客户端"。它是手机端聊天的远程 UI——所有加密 / 解密 / 签名 / 密钥存储都在手机上完成,浏览器端只负责:
- 显示消息文本
- 接收键盘输入 → 发给手机
- 显示发送状态
浏览器端从不接触任何加密密钥,也从不直接加密 / 解密任何消息。这点在隐私架构里很重要——就算浏览器被恶意扩展渗透,攻击者也拿不到加密密钥。