Signal-Server — 服务端看不到任何明文的即时通信后端
是什么
Signal-Server 是 Signal 这款即时通信 App 的服务端实现——账号注册、设备同步、消息中转、推送下发、群组、联系人查找都跑在它身上。但它有一个反常识的设计:服务端自己看不到任何一条消息的内容。
日常类比:像快递公司的分拣中心。包裹(消息)从寄件人到收件人都封着不透明的箱子,分拣中心只看箱子上的”地址”(账号 + 设备 ID),把它送到下一站,箱子里装什么从头到尾不知道。
客户端 A -- 加密信封 --> Signal-Server -- 同样的信封 --> 客户端 B (服务端只读地址,不能拆)技术上这是用 Java 17 + Dropwizard 写的标准 Web 服务,账号和消息队列存在 FoundationDB,在线状态走 Redis,长连用 WebSocket,推送解耦给苹果 APNs / 谷歌 FCM。加密本身不在这里——加密在两端的客户端用 Signal Protocol 完成,服务端只是一个”特别小心的快递员”。
为什么重要
不理解 Signal-Server,下面这些事都说不清楚:
- 为什么 WhatsApp / Messenger / Skype 都借鉴了 Signal Protocol,但服务端实现各家各做
- 为什么”端到端加密”的关键在客户端,但服务端的设计同样决定隐私下限(元数据泄漏)
- 为什么自己架一个 Signal-Server 仍然不够私密——通讯录、谁给谁发过消息这些元数据还在
- 为什么 Signal 反复强调”我们也读不到你的消息”——这不是营销话术,是后端架构强制保证的
核心要点
服务端”装作没看见”靠这三件事:
-
信封模型:每条消息进服务端时,外层是个”地址 + 时间戳”的明信片,里层是一坨密文。服务端只读外层。类比:信封上的邮编、收件人姓名能看,信纸上的字看不到。
-
多设备扇出:一个用户可能在手机、桌面、平板上同时登录。寄件人的客户端要把同一条消息为每台设备各加密一份,服务端再分别投递。服务端因此不能”广播一份”省事——它根本没有那把密钥。
-
私密计算保护查找:当你装好 Signal 想看通讯录里谁也用 Signal,服务端不能拿到你的整本通讯录。Signal 把这一步放进了 Intel SGX / AWS Nitro 的可信计算飞地(enclave),客户端可以远程验证”飞地里跑的是公开审计过的代码”,再把数据交进去。
三件事加起来,让服务端从”理论上读不到”升级成”机制上读不到”。
实践案例
案例 1:A 发一条消息给 B 时,服务端到底做了什么
1. A 客户端:用 Signal Protocol 给 B 的每台设备加密一份密文2. POST /v1/messages/{B 账号} → Signal-Server3. 服务端校验 A 的认证 token、检查 B 在线4. B 在线:通过 WebSocket 直接推 B 不在线:把信封塞进 FoundationDB 的待投队列5. 给 B 的所有设备发一条"有新消息"的推送(载荷为空,仅唤醒)6. B 上线后调 GET /v1/messages 取走 + 返回 ACK,服务端才删服务端全程没有解密能力——它的代码里根本没那把私钥。
案例 2:私密联系人发现
朴素做法:客户端把通讯录上传,服务端比对返回交集 ↑ 服务端拿到了你认识的所有人
Signal 做法:1. 客户端用远程证明确认服务端 enclave 跑的是公开代码2. 通讯录加密后送进 enclave3. enclave 内做比对,输出加密后的交集,服务端拿不到中间结果代价:要维护一套 enclave 服务,部署门槛比普通 HTTP 高一个数量级。
案例 3:自架一台 Signal-Server 的最小依赖盘点
必备:- Java 17 + Maven- FoundationDB 集群(账号 / 消息队列)- Redis(在线状态、限流计数)- 推送凭证:APNs p8 证书 + FCM service account- 域名 + TLS 证书(客户端做证书钉扎)- 一台 turn server(语音 / 视频通话穿透 NAT)
可选但实际要:- 私密联系人发现的 enclave 服务- 反垃圾子模块(spam-filter)- 日志、监控、告警基础设施跑起来不难,跑得像 Signal 那样隐私才难。
踩过的坑
-
服务端事后想补 E2E 几乎不可能:先按”服务器是受信”建好的系统,账号、群组、消息历史都在服务端有明文备份,要改成端到端意味着推翻数据模型——所以 Signal 一开始就把”服务端看不到”当架构红线。
-
元数据是最大破口:内容加密了,但”谁在什么时间给谁发过”这张图还在服务端。Signal 用密封发件人(sealed sender)让服务端连寄件人是谁都不知道,但社交图仍可能被请求模式反推。
-
推送通道是第三方信道:APNs / FCM 由苹果 / 谷歌运营。如果你把消息预览塞进推送载荷,等于在最后一公里把 E2E 打穿了。Signal 的推送只是空响铃,让客户端起来自己取。
-
WebSocket 重连风暴:手机网络一切就抖动,几亿台设备同时重连能把任何后端打挂。必须做指数退避、抖动随机化、服务端会话粘连,重连成本要远低于完整握手。
适用 vs 不适用场景
适用:
- 学习”零信任服务端”的工程做法——账号、消息、密钥、查找的边界划在哪里
- 自架小型私密通信(公司内部 / 政府 / 记者团体)
- 对端到端加密协议感兴趣,想看协议外围的工程支撑(认证、注册、推送、密钥包分发)
不适用:
- 想要服务端做内容审核、广告匹配、消息搜索——结构上做不到,强行做要拆掉 E2E
- 想要联邦化通信(不同服务器互通)——这是 Matrix 的目标,Signal 故意选了中心化以收紧攻击面
- 想要做”我替你存储完整聊天记录”的云备份——Signal 的设计不存历史,你也不应该重新打开这个口子
历史小故事(可跳过)
- 2010 年:Moxie Marlinspike 创立 Open Whisper Systems,做出 TextSecure,前身就是 Signal-Server。
- 2014 年:TextSecure 与 RedPhone 合并改名 Signal,服务端仓库随之改名 Signal-Server。
- 2016 年:WhatsApp 在十亿用户上启用 Signal Protocol,证明这套 E2E 设计能扛大规模。
- 2018 年:私密联系人发现服务上线,第一次大规模商用 SGX enclave。
- 2022 年:服务端切到 FoundationDB,账号与消息队列存储统一。
学到什么
- 隐私不是事后补丁,是开局红线——一个”服务端可以看一眼”的系统永远改不成 E2E。
- 元数据的威胁常被低估:内容加密只是第一步,谁联系谁、什么时候联系,本身就是高敏感信号。
- 可信硬件 + 远程证明 是当前对”私密查找”最务实的工程答案,但部署门槛高。
- 简单的依赖栈也能服务亿级用户:Java + Dropwizard + FoundationDB + Redis,没有银弹,关键在边界划得对。
延伸阅读
- 论文:The Double Ratchet Algorithm(Signal Protocol 核心算法规范)
- 论文:X3DH Key Agreement Protocol(异步初始密钥协商)
- 博客:Technology Preview: Sealed Sender for Signal(密封发件人怎么实现)
- 博客:Private Contact Discovery with SGX(enclave 联系人发现工程实录)
- matrix-rust-sdk —— 联邦化路线的对照组,看两种设计取舍
关联
- dropwizard —— Signal-Server 选用的轻量 Java Web 框架,决定了服务端的技术口味
- matrix-rust-sdk —— Matrix 走联邦化、Signal 走中心化,同一问题的两条路线
- element-android / element-web —— Matrix 阵营的客户端,可与 Signal 客户端做架构对照
- dendrite —— Matrix 的 Go 服务端,结构上对应 Signal-Server 但信任模型完全不同
- [[tls-1.3]] —— Signal 长连的传输层基础,配合证书钉扎收紧 MITM 攻击面
- redis —— 在线状态与限流的 KV 引擎选择
- envoy —— 大规模 WebSocket 长连入口的常见前置代理
反向链接
- dendrite —— Dendrite — Go 写的第二代 Matrix homeserver,组件可拆可合
- dropwizard —— Dropwizard — Java 微服务的”开箱即用 12-factor 起步包”
- ejabberd —— ejabberd — Erlang 写的电信级 XMPP/MQTT 多协议服务器
- element-android —— Element Android — Matrix 协议官方 Android 客户端(Kotlin + Realm)
- element-web —— Element Web — Matrix 协议旗舰 web 客户端(React + matrix-js-sdk)
- envoy —— Envoy — 把网络通信从业务代码里抠出来的代理进程
- haraka —— Haraka — 用 Node.js 写插件链式架构的 SMTP 服务器
- libsignal —— libsignal — 端到端加密的 Rust 内核
- mailcow —— mailcow — Docker compose 一键起一整套邮件服务
- matrix-rust-sdk —— matrix-rust-sdk — Matrix 客户端的”共享发动机”
- mattermost —— Mattermost — Slack 的开源自托管替代(Go 服务端 + React 客户端)
- prosody —— Prosody — Lua 写的轻量 XMPP 服务器(嵌入式部署 + 模块化插件)
- redis —— Redis — 内存键值数据库
- rocket-chat —— Rocket.Chat — 开源 Slack 替代,Meteor + MongoDB 全栈实时聊天
- signal-android —— Signal Android — 让 Android 上的每条消息都只有两端能看见
- signal-ios —— Signal iOS — 让 iPhone 上的每条消息都只有两端能看见