这是一个基于零信任架构 (Zero-Trust Architecture) 和 端到端加密 (E2EE) 的现代通讯系统演示项目。
本项目摒弃了传统的明文密码传输和基于会话 (Cookie/JWT) 的弱认证机制,转而采用纯密码学证明。在此架构中,服务器被降级为一个“盲目的公证人”和“加密数据存储节点”。服务器永远无法接触到用户的明文密码、私钥或业务数据的明文内容。
此外,为了追求极致的网络性能和代码优雅,项目在网络传输层摒弃了臃肿的 JSON 和 Base64 编码,全面采用 Protocol Buffers (Protobuf) 进行纯二进制流传输。
- 后端: Cloudflare Workers + Hono
- 数据库: Drizzle ORM + Cloudflare D1 (SQLite, 纯
BLOB二进制存储) - 客户端: Node.js (纯 V8
Web Crypto API环境模拟) - 序列化与传输:
@bufbuild/protobuf(生成纯净的 TS POJO 类型) + Axios (arraybuffer响应机制) - 密码学算法 (完全基于 Web Crypto API):
- Ed25519: 用于设备身份标识和数字签名(双向认证)。
- X25519 (ECDH): 用于握手阶段的动态密钥协商 (Forward Secrecy)。
- AES-256-GCM: 用于对业务全字段进行带认证的对称加密。
- HMAC-SHA256: 用于对传输报文进行二次防篡改和防伪造校验。
- PBKDF2: 用于基于密码的密钥派生 (脑钱包)。
本项目包含四个核心的安全流程:注册、握手 (登录)、安全收发 和 设备恢复。为了方便理解,以下是高度简化的核心加密与通信交互序列图:
sequenceDiagram
participant C as 客户端 (Client)
participant N as 不可信网络 (Network)
participant S as 零信任服务器 (Server)
Note over C,S: 1. 层次化加密注册 (Tiered Encryption Registration)
C->>C: 生成 身份密钥对 (Ed25519) & 随机 DEK
C->>C: 使用 DEK 加密 身份私钥 (WrappedPrivKey)
C->>C: 使用 PBKDF2 派生 KEK_pass & KEK_rec
C->>C: 用 KEKs 包裹 DEK (WrappedDEKs)
C->>N: 发送 [身份公钥, 盐值, WrappedPrivKey, WrappedDEKs]
N->>S: 转发注册包
S->>S: 存入数据库 (零知识存储,无明文私钥/DEK)
Note over C,S: 2. 握手与双向认证 (Handshake / Login)
C->>C: 生成 临时密钥对 (X25519) & ClientNonce
C->>S: Step 1: 发送 [clientId, Client临时公钥, ClientNonce]
S->>S: 生成 临时密钥对 (X25519) & ServerNonce & Timestamp
S->>S: 使用 服务器长期私钥 对上下文进行签名
S->>C: Step 2: 返回 [sessionId, Server临时公钥, ServerNonce, Timestamp, 服务器签名]
C->>C: 验证 服务器签名 (确认服务器合法性)
C->>C: 使用 客户端身份私钥 对上下文进行签名
C->>S: Step 3: 发送 [sessionId, 客户端签名]
S->>S: 验证 客户端签名 (确认用户身份)
Note over C,S: 双方本地利用 ECDH + HKDF 计算出会话密钥 (AES-GCM) & HMAC密钥
Note over C,S: 3. 全加密业务通信 (Secure Communication)
C->>C: 业务 JSON 数据使用 会话密钥 加密 (AES-GCM)
C->>C: 使用 HMAC 密钥计算防篡改签名
C->>N: POST [Protobuf 二进制密文包 + HMAC + Nonce]
N->>S: 转发密文包
S->>S: 验证 HMAC,验证通过后解密提取 JSON 业务数据
S->>S: 处理业务并加密生成返回包裹
S->>C: 返回 [Protobuf 二进制密文包 + HMAC + Nonce]
Note over C,S: 4. 账户恢复 (Account Recovery)
C->>S: 请求拉取 盐值 与 被包裹的密钥数据
S->>C: 返回加密数据包
C->>C: 用户输入 RecoveryCode
C->>C: PBKDF2 还原出 KEK_rec,解开 DEK
C->>C: 使用 DEK 解开 身份私钥
C->>C: 成功恢复身份,可进行后续握手或重置凭据
用户注册时,通过“信封加密”机制确保私钥的绝对安全。详细设计请参考 KEY_HIERARCHY.md。
- 密钥生成:客户端本地随机生成 身份密钥对 (Ed25519) 和一个随机的 数据加密密钥 (DEK, AES-256)。
- 第一层加密 (私钥保护):使用 DEK 加密身份私钥,得到
wrappedPrivKey。 - 第二层加密 (密钥包裹):
- 密码路径:使用
PBKDF2(password + salt)派生出 KEK_pass,并用其包裹 DEK 得到passwordWrappedDek。 - 恢复路径:使用
PBKDF2(recoveryCode + salt)派生出 KEK_rec,并用其包裹 DEK 得到recoveryWrappedDek。
- 密码路径:使用
- 零知识存储:客户端将
userId、身份公钥、两个盐值以及三个加密包裹(wrappedPrivKey,passwordWrappedDek,recoveryWrappedDek)发送给服务器。 - 结果:服务器无法接触明文 DEK,更无法解开身份私钥。即使数据库泄露,攻击者也面临双重加密屏障。
采用变种 Sigma 协议 风格的握手,确立前向安全性。详细步骤请参考 HANDSHAKE.md。
- Step 1 (Client 发起): 客户端生成临时密钥 (
clientTempPubKey, X25519) 和clientNonce发送给服务器。 - Step 2 (Server 响应与证明): 服务器生成临时密钥 (
serverTempPubKey, X25519) 和serverNonce。使用服务器长期私钥 (Ed25519) 对上下文(双方临时公钥 + Nonce + 时间戳)进行签名,连同sessionId返回。 - Step 3 (Client 验证与证明): 客户端验证服务器签名。确认无误后,使用自己的身份私钥 (Ed25519) 对上下文进行签名并发回。
- 最终确立: 服务器验证客户端签名。双方通过 ECDH 计算共享秘密,并通过 HKDF(融合双向 Nonce)派生出:
- 会话密钥 (
sessionKey, AES-256-GCM):用于业务加密。 - 签名密钥 (
hmacKey, HMAC-SHA256):用于报文防篡改。
- 会话密钥 (
业务数据的收发完全是黑盒的。
- 加密: 业务 JSON 数据序列化后使用
sessionKey(AES-256-GCM) 加密。 - 防伪造: 生成
nonce和timestamp,并将这些报文头连同密文一起,使用hmacKey计算出 HMAC 签名。 - 传输: 将字段封装进 Protobuf
SecurePayload二进制流进行传输。 - 解密与校验: 接收方先验证 HMAC,通过后再验证 GCM Auth Tag 并解密出明文 JSON。
当用户更换设备或丢失身份私钥时:
- 拉取包裹:新设备从服务器获取盐值和
recoveryWrappedDek及wrappedPrivKey。 - 本地还原:用户输入
recoveryCode,客户端派生 KEK_rec,解开recoveryWrappedDek得到 DEK,再用 DEK 解开wrappedPrivKey恢复身份私钥。 - 身份重置:利用恢复出的私钥生成新的身份证明,完成对账号的重新控制。
本项目的设计初衷是探索在极端安全威胁(甚至包括“后端不可信”的情况)下的解决方案。以下是常见的网络攻击场景以及业界标准的零信任密码学缓解机制:
- 潜在隐患 (为什么 TLS 仍可能面临风险?):
- 信任根的脆弱性: 现代 Web 系统高度依赖 TLS 保护传输。然而,TLS 的安全性建立在操作系统的根证书信任链上。如果用户处于受限网络(如企业内网强制安装根证书、防病毒软件拦截、或在公共 Wi-Fi 被诱导安装了伪造证书),攻击者即可实施中间人攻击,导致所有传输内容(包括密码、Cookie、业务数据)被明文截获。
- 传输终结问题: 即使没有恶意拦截,合法的网络节点(如 CDN、WAF、负载均衡器)也会在接入层终结 TLS 加密。这意味着数据在离开 CDN 进入内部网络时,往往以明文形式存在。
- 零信任架构缓解机制 (密码学双向认证):
- 应用层前向保密 (Forward Secrecy): 每次握手均生成全新的
X25519临时密钥用于 ECDH 交换。即使攻击者完整记录了历史流量并在未来获取了某一方的长期私钥,也无法推导出过去的会话密钥,从而保护历史通信内容。 - 强双向身份认证: 临时密钥的交换被双方的长期身份密钥 (
Ed25519) 严格签名。攻击者在网络层无法伪造合法的 Ed25519 签名,因此无法完成密钥替换,从而切断了中间人欺骗的可能性。
- 应用层前向保密 (Forward Secrecy): 每次握手均生成全新的
- 潜在隐患: 攻击者无需解密数据,只需截获用户发送过的有效请求(如转账指令),然后向服务器重复发送。如果系统仅依赖静态 Token,这些重放请求可能会被错误地多次执行。
- 零信任架构缓解机制 (Nonce + Timestamp + HMAC):
- 报文防篡改与唯一性: 每一个 Protobuf 业务请求 (
SecurePayload) 在加密前,不仅生成了唯一的nonce(12字节高强度随机数),还附带了当前的绝对timestamp。 - HMAC 全局签名: 整个报文体被
HMAC-SHA256进行签名。修改时间戳会导致签名失效;而对于未修改的重放请求,服务器可通过核对近期的nonce或设置严格的时间戳窗口(如仅接受 30 秒内的请求)来直接丢弃过期或重复的报文。
- 报文防篡改与唯一性: 每一个 Protobuf 业务请求 (
- 潜在隐患: 应用通常在数据库中存储密码哈希(如 bcrypt)。一旦数据库发生泄露,攻击者可通过离线算力进行暴力破解,不仅威胁当前账号,还可能导致用户在其他平台的同名账号遭到撞库攻击。
- 零信任架构缓解机制 (Zero-Knowledge):
- 零密码哈希: 数据库中不存储任何密码或密码的哈希值。
- 本地密钥派生: 密码和恢复码仅存在于客户端的瞬时内存中。客户端使用它们通过
PBKDF2-HMAC-SHA256(100,000次迭代) 派生出高强度的数据加密密钥 (KEK)。 - 加密存储: 数据库中唯一与凭据相关的是被上述 KEK 使用
AES-256-GCM加密的私钥包 (encryptedRecoveryPrivKey)。即便数据库泄露,攻击者也面临极高的算力成本来盲猜密码组合以尝试解密。
- 潜在隐患: 服务器端通常拥有最高权限。内部人员作恶或服务器被植入后门时,攻击者可以轻易在内存中提取用户密码、读取业务记录或伪造用户身份。
- 零信任架构缓解机制 (身份剥离与局限性):
- 身份不可伪造: 用户的核心凭据是本地生成的长期私钥。服务器仅持有公钥和无法解密的私钥包,由于无法伪造合法的 Ed25519 签名,服务器无法冒充用户发起合法请求。
- 局限性声明 (当前为 C2S 架构):
- 目前的状态 (C2S): 当前的加密隧道终结于服务器(服务器参与了 ECDH 密钥协商并持有 Session Key)。这意味着,虽然防范了网络窃听和数据库泄露,但运行在服务器内存中的代码在解密后依然可以接触到明文业务数据 (
decryptedBody)。 - 迈向全链路端到端 (C2C): 若业务场景要求服务器也必须是“零知识”的(如高隐私 IM 软件),当前的架构已提供基础。只需在前端引入基于接收方
clientLongPubKey的二次 ECDH 密钥协商,将业务消息在客户端侧再次加密后封装入SecurePayload.cipherText。如此,服务器将完全降级为仅负责路由和存储的不可信中继节点。
- 目前的状态 (C2S): 当前的加密隧道终结于服务器(服务器参与了 ECDH 密钥协商并持有 Session Key)。这意味着,虽然防范了网络窃听和数据库泄露,但运行在服务器内存中的代码在解密后依然可以接触到明文业务数据 (
- 潜在隐患: 传统系统常使用 Cookie 或 JWT 作为会话凭据。如果这些 Token 通过 XSS 攻击或物理接触被窃取,攻击者可以在有效期内完全控制用户账户,且通常难以检测。
- 零信任架构缓解机制 (双向派生密钥):
- 私有的会话状态: 本架构中的
sessionKey和hmacKey是通过双方的临时私钥和对方的临时公钥在本地计算得出的。这些密钥从未在网络上出现过,也未存储在本地持久化设备(如 LocalStorage)中。 - 一次性上下文: 即使攻击者获取了内存中的会话密钥,该密钥也仅对当前 sessionId 有效。由于每次通讯都伴随着 Nonce 和签名校验,攻击者无法轻易在其他上下文中使用这些密钥。
- 私有的会话状态: 本架构中的
- 潜在隐患: 攻击者常尝试通过高频登录接口猜测用户密码。即便后端有频率限制,攻击者也可以通过拖库后进行离线攻击。
- 零信任架构缓解机制 (客户端算力开销):
- 离线攻击免疫: 由于服务器不存储密码哈希,即使数据库泄露,攻击者也无法进行直接的哈希碰撞测试。
- 强制的算力开销: 密钥派生算法 (PBKDF2) 被放置在客户端执行。这意味着任何尝试猜测密码的行为都会在攻击者侧产生巨大的算力成本(100,000次迭代)。由于注册和恢复逻辑要求提供正确的加密结果,这种架构将防护前置到了计算成本最高的一侧。
- 潜在隐患: 在某些攻击场景下,攻击者不解密报文,而是通过修改二进制流中的某些位(位翻转攻击),试图在解密后诱导逻辑错误(如修改转账金额)。
- 零信任架构缓解机制 (认证加密机制):
- AEAD 保护: 系统采用 AES-256-GCM。GCM 是一种 AEAD (Authenticated Encryption with Associated Data) 模式,它在加密的同时生成一个身份认证标签 (Tag)。
- 严格校验: 如果密文被修改了哪怕一个 bit,解密方的 Web Crypto API 会在底层直接抛出
OperationError。配合外层的 HMAC-SHA256 签名,系统实现了双重的数据完整性校验,彻底杜绝了静默修改数据的可能。
- 潜在隐患: 如果系统使用固定的长期密钥加密数据,一旦该密钥在未来某天泄露(例如服务器硬盘被物理获取),攻击者就可以解密之前拦截并存储的所有历史报文。
- 零信任架构缓解机制 (前向保密):
- 临时密钥交换: 每次会话协商均使用基于
X25519的短寿命临时密钥对。 - 不可逆性: 会话密钥仅存在于内存中,并在会话结束后销毁。即使长期身份密钥 (
Ed25519) 泄露,由于它仅用于签名而非加密,攻击者依然无法从历史流量中推导出已销毁的临时密钥,从而保护了历史数据的安全性。
- 临时密钥交换: 每次会话协商均使用基于
- 潜在隐患: 即使内容加密,攻击者仍可以通过分析报文的大小、发送频率和目标地址(元数据)来推断用户的行为模式。
- 缓解机制与客观局限:
- Protobuf 紧凑化: 采用二进制 Protobuf 减少了报文的特征指纹,使其比 JSON 更难通过简单的深度报文检测 (DPI) 进行识别。
- 局限性承认: 零信任架构主要解决的是身份和内容的安全。元数据隐私(如 IP 地址、在线状态、联系人图谱)在目前的 C2S 甚至 C2C 架构中通常仍对服务器可见。若需进一步缓解此类隐患,通常需要引入混合网络 (Mixnets) 等更复杂的隐私技术。
- 潜在隐患: 攻击者可能尝试强迫客户端回退到不安全的协议版本(如明文 HTTP),或利用复杂的 JSON 解析漏洞实施攻击。
- 零信任架构缓解机制:
- 强类型约束: 整个系统强制使用 Protobuf 序列化,不接受任何非预定义的二进制流。由于 Protobuf 的解析逻辑比 JSON 类库更稳健,极大地减少了反序列化带来的安全风险面。
- 全栈二进制化: 从数据库 BLOB 到网络字节流的一致性,消除了常见的编码转换(如字符集注入)带来的潜在攻击点。
即便在极端情况(如 TLS 被攻破、根证书被劫持、或在 CDN/WAF 处被强制终结)下,本系统的应用层加密设计依然能提供坚实的底层防护。
如果攻击者(如恶意网关、被劫持的 ISP)实施了 TLS 中间人攻击,他们能看到所有的 HTTP 流量(包括 URL、Header 和 Protobuf 序列化后的二进制数据),但核心资产依然处于加密保护之下:
- 业务数据密文 (
/api/data):攻击者截获的cipher_text经过 AES-256-GCM 加密,解密必须持有通过 ECDH 协商出的sessionKey。 - 会话密钥安全性:
sessionKey通过 X25519 (ECDH) 协商。攻击者虽然能看到双方交换的临时公钥,但受限于椭圆曲线离散对数难题,无法在有限时间内推算出共享秘密。 - 请求与响应的真实性:由于攻击者没有
hmacKey,任何对二进制报文的静默篡改都会导致 HMAC 校验失败,服务器或客户端会直接丢弃非法报文。 - 身份不可伪造:握手阶段通过 Ed25519 签名绑定了上下文。攻击者没有服务器或客户端的长期私钥,无法伪造合法的握手响应或请求。
- 密码与恢复凭据安全:注册与恢复流程中传输的是经过 KEK 加密的私钥包,网络中从未出现过明文密码或派生后的 KEK。
结论:在 TLS 完全失效的情况下,本系统依然成功防守住了数据的机密性和完整性。这证明了应用层 E2EE 架构在零信任环境下的核心价值。
当 TLS 保护壳被剥离后,裸奔在网络上的 Protobuf 报文会泄漏以下元数据 (Metadata):
- 身份标识:
RegisterRequest,RecoveryRequest,HandshakeStep1Request,SecurePayload中的user_id/client_id。 - 会话轨迹:
HandshakeStep1Response,HandshakeStep3Request,SecurePayload中的session_id(一串随机 UUID)。 - 时间指纹:报文中的
timestamp。 - 公钥指纹:各种阶段交换的临时公钥和长期公钥。
攻击者能利用这些元数据做什么? 虽然无法破解通讯内容,但通过流量分析 (Traffic Analysis),攻击者可以精确推断:
- 用户画像:谁正在使用该系统(通过获取
user_id)。 - 活跃规律:用户何时上线(监控握手接口)以及通信频率(监控带有特定
session_id的 API 请求)。 - 物理位置:结合出口 IP 地址和
user_id锁定具体用户的网络位置。
对于高度敏感的系统,元数据的暴露同样可能导致致命后果。
若安全目标要求在“TLS 不可靠”的前提下依然保护用户隐私,可采取以下演进策略:
- 身份加密 (Identity Hiding):
- 客户端预置或缓存服务器的长期公钥。
- 在握手第一阶段,客户端使用服务器公钥对自己的
user_id进行非对称加密后再发送。 - 服务器解密后获取身份。攻击者在网络线上看到的将是无法关联的加密 ID。
- 会话脱敏 (Session Minimization):
- 在
SecurePayload中彻底移除client_id字段。 - 服务器仅通过随机的
session_id查找会话上下文。攻击者看到的将只是一串无意义的 UUID,难以将流量与具体用户关联。
- 在
- 匿名路由:引入类似 Signal 的 Sealed Sender 技术或洋葱路由,彻底切断发送者与服务器/接收者之间的强元数据关联。
为了构建稳固的信任模型,本系统在协议设计中参考了业界成熟的密码学绑定策略:
- 设计初衷: 在密码学交互中,单纯的身份证明有时不足以防范“身份搬运”类攻击。
- 业界标准方案: 在签名报文中包含对端的临时公钥(Ephemeral Public Key Binding)。这种做法相当于在证明自身身份的同时,明确标注了“该证明仅对当前特定的通讯对象有效”。这可以有效预防攻击者将合法签名转发给第三方的中间人接力攻击,因为任何第三方都会发现签名中绑定的公钥与自身的临时公钥不符,从而识别出潜在的欺骗行为。
- 设计初衷: 减少无效请求对后端资源的占用,并防御潜在的扫描行为。
- 业界标准方案: 服务器遵循“先验证、后处理”的原则。在握手阶段首步即要求客户端提供 ID,并在派生高开销的会话密钥前,强制校验客户端提供的 Ed25519 签名。只有能够证明自己持有与云端预存公钥相匹配的私钥的客户端,才能完成后续的会话确立流程。
- 设计初衷: 在会话生命周期内提供持续的数据保护。
- 业界标准方案: 在双向验证完成后,双方通过 ECDH 计算出相同的共享秘密并以此派生会话密钥。后续所有业务报文均使用该共享密钥进行AES-GCM 全字段加密,并附加 HMAC 认证。这种设计旨在确保:即便底层网络设施完全不可信,攻击者也无法解密敏感数据,无法进行静默篡改,更无法伪造通信任何一方的身份。
项目使用 pnpm 作为包管理器,分为 api (后端)、client (前端模拟) 和 proto (协议共享库) 三个 workspace。
npx pnpm installcd packages/api
# 将 Drizzle Schema 生成 SQL 并应用到本地 Cloudflare D1 模拟环境
npm run db:generate
npm run db:migrate:local
# 启动本地开发服务器 (默认运行在 http://localhost:8787)
npm run devcd packages/client
# 启动客户端 Demo
npm start运行效果预期: 您将在终端看到完整的客户端执行日志,依次演示:
Zero-Trust Registration:本地进行高强度运算并注册。Initiating Handshake:ECDH 握手成功,建立安全会话。Sending secure data:通过 Protobuf 成功发送和接收全加密载荷。Simulating New Device Recovery:模拟一台全新的设备,仅凭密码和恢复码成功完成密钥复原和身份替换。