Files
blog/content/posts/e2e-false-need.md
T

98 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "E2E 是个伪需求"
description: "配置中心的端到端加密方案,上午落地,下午死在了自己的安全评估里。不问威胁模型就堆加密原语,堆得越专业越危险。"
date: 2026-08-24T20:25:00+08:00
draft: false
tags:
- 架构
- 安全
- 配置中心
---
## 背景
项目里散落着六处配置:客户端直连的对象存储密钥、推送 Token、第三方 API Key……全部硬编码在客户端代码里。今天要把它们收进统一配置中心,核心诉求一句话:
> 这些核心配置不能被泄露,不能在被人逆向时直接拿到。
围绕这句话,方案迭代了三轮,最终回到了一个最初根本没考虑过的答案。
## 方案 C:看起来很专业
上午拍板的是一套「按凭据类型区分加密」的设计:
| 凭据类型 | 前缀 | 存储 | 服务端回显 |
|---------|------|------|-----------|
| 纯客户端凭据 | `cred_` | **客户端公钥混合加密**RSA-OAEP + AES | ❌ 服务端无私钥,不可见 |
| 服务端可管理 | `scred_` | 对称加密 | ✅ 可回显编辑 |
听起来很美:客户端上传公钥,配置中心用公钥加密,数据库里全是密文,连服务端管理员都看不到明文--教科书级的端到端加密(E2E)。落地、验证、全绿。
下午做安全评估的时候,它塌了。
## 评估:三个致命缺陷
### 1. 公钥踩踏
一份配置要下发给多个客户端,加密时用**谁的**公钥?A 的公钥加密,B 就解不开;给每人存一份密文,配置更新就要全量重加密。E2E 假设「一对一私聊」,而配置中心是「一对多广播」--场景就不匹配。
### 2. 公钥冒领
公钥是**公开的**。任何人都能拿到公钥,加密一份假凭据提交上来,服务端无私钥无法验证内容真伪,只会原样存储、原样下发。你防住了自己人看,没防住别人写。
### 3. 两端都有明文,E2E 防的是谁?
这是最根本的一问。E2E 的威胁模型是「**服务端不可信**」(即时通讯怕服务器运营商偷看)。可我们的场景:
- 客户端拿到配置后**本来就要在内存里解密成明文来用**
- 服务端是自己写的、自己部署的,本来就在信任边界内
**真正想防的「逆向」发生在客户端,而 E2E 加密的是客户端到服务端这一段。** 加密原语堆得再高级,防的都不是你要防的那个人。
## 点破:物理冲突与真正的命门
继续往下问,会碰到一个无解的矛盾:
> 「客户端要用凭据」和「客户端逆向拿不到凭据」,物理冲突。
客户端要用,就必须持有明文(至少在内存里);能防的边界只有**反编译静态分析**(99% 的场景),动态调试 dump 内存防不了--这条要诚实承认,任何声称「客户端防动态逆向」的方案都是自欺。
再回头看真正的命门:**就算凭据全部移到服务端,客户端代码里还硬编码着一个静态 API Key**。逆向者拿到这个 Key,照样能调用接口把全部凭据拉走。前面所有加密等于白忙。
结论浮出水面:**这个场景的安全关键根本不在加密,在鉴权。**
## 最终方案:最简三步
推翻方案 C 之后,剩下的东西少得可怜:
1. **删掉整个 E2E**:公钥上传、混合加密、双前缀,全删
2. **存储对称加密 + 后台脱敏**:数据库泄露不泄明文,界面回显一律 `****`--这一步防的是「数据库被拖」
3. **鉴权换动态登录**:工号 + 密码换短期 JWT,凭据经 TLS 下发、内存用完即弃、不落盘--这一步防的是「逆向拿到静态钥匙拉走全部」
删掉的代码比新写的还多。验证清单反而更短更硬:有 JWT 才能拿配置(无 token 401)、错误密码拒绝、存储确认为密文。
## 同一天的另外三次「推翻」
今天的主题像是「推翻自己」,不止这一次:
**二维码 404 双层修复**。弹窗打不开,第一层根因是 Bootstrap 5 移除了 jQuery 插件集成,八处旧写法全部改原生 API。改完发现页面还是坏的--真正的 404 在**用户实际访问的公网入口**:那边只反代了 PHP 请求,静态资源落在本地目录全是 404。教训:只测内网等于没测,验证必须覆盖用户真实入口。
**下载量统计全 0**。不是统计代码有 bug,是客户端走渠道轮转**直连渠道下载**,根本没经过服务端,自然不计数。修复不是改计数逻辑,而是补一个「下载完成才上报」的端点,顺手用 MD5 投票机制(三票阈值、三天窗口、投票人去重)让多个客户端互相校验文件哈希。
**32 条 FAIL 日志**。扫描出 32 条失败记录,逐条分类后:31 条是设计内的拦截(二选一城市待确认、多候选客户待澄清、在保不派单……),只有 1 条是真 bug--无操作人参数时误报「凭证过期」,实际该报「写操作必须指定操作人」。红色不等于故障,**先分类再动手**。
## 总结
四件事,四个同款教训:
1. **先问威胁模型,再选加密方案**。E2E 防「服务端不可信」,数据库加密防「拖库」,动态鉴权防「逆向拉取」--三个威胁,三套解法,混着用只会互相拖累。
2. **鉴权优先于加密**。加密保护数据形态,鉴权决定数据归属。静态钥匙在手里,保险柜再厚也是空的。
3. **验证要覆盖真实入口**。内网全绿的系统,公网入口可能全是 404。
4. **最简方案往往更安全**。攻击面小、可审计、能讲清楚。复杂度本身也是一种漏洞。
> 方案 C 不是死于实现太糙,是死于**问错了问题**。安全设计的第一步不是选算法,是画威胁模型--搞清楚你到底在防谁。
---
*本文已脱敏,不含真实域名、IP、密钥或用户数据。*