跳到正文

案例研究PRIVY.SYS2025

Privy:端到端加密即时通讯

这个项目经过毫不留情的审计后,教会了我什么是安全与架构。

  • 9个重大缺陷2026 年 7 月审计
  • 7个类别安全、性能、网络…
  • 8项改进如果今天重做
[ POST_MORTEM.LOG ]已归档 • 教学性概念验证

[01]背景

Privy 源于我想设计一个不依赖中心化巨头的保密即时通讯的愿望。目标是零知识原则:服务器只应是加密数据包的盲中继,既不能读取对话,也不能获得对称密钥。

功能:用昵称注册并在设备上生成密钥对、安全邀请、带输入状态提示的实时交流、通过 Capacitor 构建的 Android 应用,以及文件共享的雏形。

[02]架构

基于 Vue 3 / Vite 和 Pinia 的客户端,通过 Capacitor 封装为 Android 应用。服务端包括:Express REST API(JWT 认证、用户资料、邀请)、负责转发加密消息的 WebSocket 服务器(ws)和 MySQL 5.7,整体用 Docker 编排。

  1. 1. 身份

    注册时,浏览器通过 Web Crypto API 生成 RSA-OAEP 2048 位密钥对(SHA-256)。公钥发送给 API 并存储在服务器上。

  2. 2. 信封

    发起方生成 AES-GCM 256 位密钥,用接收方的公钥加密后放入邀请发送。接收方用本地私钥解密。

  3. 3. 消息

    每条消息在本地用 AES-GCM 加密,12 字节的随机 IV 放在密文之前,再进行 Base64 编码。WebSocket 服务器只收到不透明的数据块。

加密流程:薄弱点(!)与修复(→)设备 ARSA-OAEP 2048AES-GCM 256! 私钥明文→ 不可导出的 IndexedDB服务器REST APIWebSocket盲中继! 每 30 秒 HTTP→ 本地校验 JWT设备 BRSA-OAEP 2048AES-GCM 2561 公钥2 RSA 信封(AES)3 AES-GCM+ IV,Base64薄弱点修复方案
加密流程。薄弱点标有“!”(粉色),修复标有“→”(橙色)。

[03]审计发现

以下是审计发现的 9 个重大缺陷,包括现象及其影响。

显示 9 个缺陷

  1. 缺陷 01 • 安全

    RSA 私钥以明文存放在 localStorage

    RSA 私钥和各会话的密钥以明文写入 localStorage:一次 XSS 就足以将它们取走,使端到端加密形同虚设。

  2. 缺陷 02 • 性能

    pbkdf2Sync 阻塞 Node.js 事件循环

    密码哈希使用 pbkdf2Sync(1000 次迭代),每次调用都会占满 Node.js 的单线程:高负载下 API 将无法响应。

  3. 缺陷 03 • 网络

    每 30 秒通过 HTTP 重新校验会话

    WebSocket 服务器无法访问数据库或共享缓存:它每 30 秒用一次 HTTP 请求重新校验每个会话,1000 个客户端约产生每分钟 2000 次请求,而这些令牌本身已带签名。

  4. 缺陷 04 • 架构

    关系模型仅限一对一

    Conversations 表依赖 creator_id 和 participant_id 两列:不重建数据库结构并重写控制器,就无法支持群聊。

  5. 缺陷 05 • 性能

    断开连接的复杂度为 O(R)

    断开连接时,WebSocket 服务器会遍历内存中的所有房间来分离 socket:开销与房间数成线性关系,而不是常数。

  6. 缺陷 06 • 数据库

    文件被切成 255 字节的块存入 MySQL

    一个 5 MB 的文件需要在数据库中写入 2 万多行。该功能在开发过程中被放弃,相关路由已停用。

  7. 缺陷 07 • 合规

    Android 权限过度申请

    清单中声明了精确定位、摄像头和麦克风,这些是在排查存储访问问题时试验性加入的:违反最小权限原则,提交 Google Play 商店时会被拒绝。

  8. 缺陷 08 • 可靠性

    过期令牌与无限重连

    JWT 只在模块求值时读取一次:登录后,其他 composable 仍持有空值,导致 401 错误,直到手动刷新页面。WebSocket 在用户主动断开后仍每 5 秒尝试重连。

  9. 缺陷 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 文件中的值是公开的。
// 最后一个案例研究返回项目列表所有项目都在首页

聊一聊

对这项工作有疑问,或想了解更多?欢迎联系我。

联系我