[01]背景
Privy 源于我想设计一个不依赖中心化巨头的保密即时通讯的愿望。目标是零知识原则:服务器只应是加密数据包的盲中继,既不能读取对话,也不能获得对称密钥。
功能:用昵称注册并在设备上生成密钥对、安全邀请、带输入状态提示的实时交流、通过 Capacitor 构建的 Android 应用,以及文件共享的雏形。
[02]架构
基于 Vue 3 / Vite 和 Pinia 的客户端,通过 Capacitor 封装为 Android 应用。服务端包括:Express REST API(JWT 认证、用户资料、邀请)、负责转发加密消息的 WebSocket 服务器(ws)和 MySQL 5.7,整体用 Docker 编排。
1. 身份
注册时,浏览器通过 Web Crypto API 生成 RSA-OAEP 2048 位密钥对(SHA-256)。公钥发送给 API 并存储在服务器上。
2. 信封
发起方生成 AES-GCM 256 位密钥,用接收方的公钥加密后放入邀请发送。接收方用本地私钥解密。
3. 消息
每条消息在本地用 AES-GCM 加密,12 字节的随机 IV 放在密文之前,再进行 Base64 编码。WebSocket 服务器只收到不透明的数据块。
[03]审计发现
以下是审计发现的 9 个重大缺陷,包括现象及其影响。
显示 9 个缺陷
- 缺陷 01 • 安全
RSA 私钥以明文存放在 localStorage
RSA 私钥和各会话的密钥以明文写入 localStorage:一次 XSS 就足以将它们取走,使端到端加密形同虚设。
- 缺陷 02 • 性能
pbkdf2Sync 阻塞 Node.js 事件循环
密码哈希使用 pbkdf2Sync(1000 次迭代),每次调用都会占满 Node.js 的单线程:高负载下 API 将无法响应。
- 缺陷 03 • 网络
每 30 秒通过 HTTP 重新校验会话
WebSocket 服务器无法访问数据库或共享缓存:它每 30 秒用一次 HTTP 请求重新校验每个会话,1000 个客户端约产生每分钟 2000 次请求,而这些令牌本身已带签名。
- 缺陷 04 • 架构
关系模型仅限一对一
Conversations 表依赖 creator_id 和 participant_id 两列:不重建数据库结构并重写控制器,就无法支持群聊。
- 缺陷 05 • 性能
断开连接的复杂度为 O(R)
断开连接时,WebSocket 服务器会遍历内存中的所有房间来分离 socket:开销与房间数成线性关系,而不是常数。
- 缺陷 06 • 数据库
文件被切成 255 字节的块存入 MySQL
一个 5 MB 的文件需要在数据库中写入 2 万多行。该功能在开发过程中被放弃,相关路由已停用。
- 缺陷 07 • 合规
Android 权限过度申请
清单中声明了精确定位、摄像头和麦克风,这些是在排查存储访问问题时试验性加入的:违反最小权限原则,提交 Google Play 商店时会被拒绝。
- 缺陷 08 • 可靠性
过期令牌与无限重连
JWT 只在模块求值时读取一次:登录后,其他 composable 仍持有空值,导致 401 错误,直到手动刷新页面。WebSocket 在用户主动断开后仍每 5 秒尝试重连。
- 缺陷 09 • 安全
机密入库,值过于简单
.env 文件自第一次提交起就被 Git 跟踪,其中的值非常简单。若原样部署,攻击者可以为任意用户伪造有效令牌。我有意在复盘中保留这些文件,作为一个我不会再犯的错误的可见示例。
[04]如果今天重做
如果按今天的标准重写 Privy,我会做出以下改变。
| 项目 | 之前(PoC) | 之后(目标) |
|---|---|---|
| 密钥 | 之前(PoC) 私钥明文存于 localStorage | 之后(目标)IndexedDB 中的不可导出密钥 |
| 密码 | 之前(PoC) 阻塞式 pbkdf2Sync | 之后(目标)异步 Argon2id |
| WebSocket 会话 | 之前(PoC) 每 30 秒一次 HTTP 请求 | 之后(目标)本地校验 JWT |
| 会话 | 之前(PoC) creator / participant 列(仅一对一) | 之后(目标)ConversationMembers 关联表 |
| 文件 | 之前(PoC) MySQL 中 255 字节的小块 | 之后(目标)S3 / MinIO 对象存储 |
| 前端状态 | 之前(PoC) JS 模块级全局变量 | 之后(目标)Pinia store,集中的 Axios 拦截器 |
| Android 权限 | 之前(PoC) 声明了 GPS、摄像头、麦克风 | 之后(目标)仅 INTERNET,其余运行时申请 |
| 密钥与机密 | 之前(PoC) .env 文件入库,值过于简单 | 之后(目标).env.example、注入强随机机密、CI 检测 |
[05]它带来的改变
它促使我学习 CCNA,加固了这个作品集(带 nonce 的 CSP、HMAC 签名部署、网络隔离),并在网站中集成了 CVE 监控。
[06]局限
- 项目已归档:这是一个教学性概念验证,绝不能部署到生产环境。
- 它依赖过时的组件(包括已终止支持的 MySQL 5.7),并存在上述缺陷。入库的 .env 文件中的值是公开的。