从 2026.1.0 版本起,支持 E2EE(端对端加密),仅面向 eSeGeCe All-Access 订阅用户。
端对端加密(E2EE)确保只有通信双方才能读取所交换消息的内容。即使负责路由消息的服务器也无法解密。本文介绍 E2EE 如何在两个对等节点之间使用公钥密码学安全地交换消息。
E2EE 原理解析
E2EE 的核心原则
在双节点 E2EE 系统中:
- 每个节点拥有一个私钥,该私钥永远不会离开其设备。
- 每个节点发布一个公钥,他人可以查看。
- 消息在发送方设备上加密,仅在接收方设备上解密。
- 服务器仅充当消息中继,不作为受信任方。
密钥材料概览
每个节点(例如 Alice 和 Bob)各自拥有:
- 私钥
本地存储的秘密值,绝不可共享。 - 公钥
由私钥派生的值,可自由共享。
公钥和私钥在数学上相互关联,但知道公钥并不会暴露私钥。
第一步:交换公钥
在开始加密通信之前,Alice 和 Bob 必须知道彼此的公钥。
常见方式:
- 服务器存储公钥,并在请求时下发。
- 在账户创建或首次联系时交换公钥。
此交换不会危及安全性,因为公钥并非秘密。
第二步:建立共享密钥(ECDH)
为了高效加密消息,Alice 和 Bob 首先使用椭圆曲线 Diffie-Hellman(ECDH)算法派生出一个共享密钥。
ECDH 工作原理概要- Alice 使用以下内容计算共享密钥:
- 她的私钥
- Bob 的公钥
- Bob 使用以下内容计算共享密钥:
- 他的私钥
- Alice 的公钥
由于椭圆曲线的数学特性,两者的计算结果相同,即获得相同的密钥值,而双方都无需在网络上传输该密钥。
共享密钥在任何时候都不会通过网络传输。
第三步:派生对称加密密钥
原始 ECDH 共享密钥不会直接用于加密,而是通过密钥派生函数(KDF)(通常为 SHA-256 等加密哈希)进行处理。
密钥派生的目的:
- 生成正确长度的密钥(例如 AES-256 需要 32 字节)
- 消除原始 ECDH 输出的结构性偏差
- 提升密码学健壮性
最终结果是一个仅 Alice 和 Bob 知道的对称加密密钥。
第四步:加密消息
当 Alice 要向 Bob 发送消息时:
- Alice 将消息转换为字节。
- Alice 使用对称加密算法(通常为 AES-GCM)和以下内容加密消息:
- 派生的对称密钥
- 随机初始化向量(IV)
- Alice 通过服务器将加密消息和 IV 发送给 Bob。
AES-GCM 常被选用,因为它提供:
- 机密性(加密)
- 完整性(防篡改检测)
- 认证性(检测伪造消息)
第五步:解密消息
当 Bob 收到加密消息时:
- Bob 使用 ECDH 和相同的 KDF 独立派生出相同的对称密钥。
- Bob 使用对称密钥和 IV 解密消息。
- 若认证成功,Bob 获得原始明文。
若消息被篡改或使用了错误的密钥,解密将失败。
服务器的角色
在此架构中,服务器:
- 下发公钥
- 路由加密消息
- 管理用户在线状态或元数据
服务器无法:
- 派生共享密钥
- 解密消息
- 伪造有效的加密消息
这是端对端加密的核心特性。
总结
两个节点之间的端对端加密通过以下三者的结合实现:
- 公钥密码学(用于密钥协商)
- 对称密码学(用于高效消息加密)
- 密钥派生函数(用于安全性和正确性)
最终构成一个:
- 只有对等节点才能读取消息
- 服务器仅承担传输角色
- 隐私由设计保障而非依赖策略
的系统。这一模型是现代安全消息系统的密码学基础。
E2EE 示例
// ... Create the Server
WSServer := TsgcWebSocketHTTPServer.Create(nil);
WSServer.Port := 80;
WSPE2EE := TsgcWSPServer_E2EE.Create(nil);
WSPE2EE.Server := WSServer;
WSServer.Active := True;
// ... Create 2 clients
WSClient1 := TsgcWebSocketClient.Create(nil);
WSClient1.Host := '127.0.0.1';
WSClient1.Port := 80;
E2EE1 := TsgcWSPClient_E2EE.Create(nil);
E2EE1.Client := WSClient1;
E2EE1.E2EE_Otpions.UserId := 'client-1';
WSClient1.Active := True;
WSClient2 := TsgcWebSocketClient.Create(nil);
WSClient2.Host := '127.0.0.1';
WSClient2.Port := 80;
E2EE2 := TsgcWSPClient_E2EE.Create(nil);
E2EE2.OnE2EEMessageText := OnE2EEMessageTextEvent;
E2EE2.E2EE_Otpions.UserId := 'client-2';
E2EE2.Client := WSClient2;
WSClient2.Active := True;
// ... client-1 send a message to client-2
E2EE1.SendDirectMessage('client-2', 'Hello Client-2');
// ... read the message in the OnE2EEMessageText event
procedure OnE2EEMessageText(Sender: TObject; const aFrom, aText: string);
begin
DoLog('#direct_message: ' + aText);
end;
