最近给一个AI应用做上线前的安全评审,我在网关前面抓了一轮流量,结果有点坐不住:用户和智能体之间的每一轮对话,包括系统提示词、被检索出来的知识库片段、用户原始输入,全部以明文形式在网络链路里流动。说的直白一点,AI的记忆在“裸奔”。
这篇文章是这个系列的第14篇。前面聊了不少AI应用工程化的东西,今天专门补上一个最基础也最容易被忽略的环节:AI数据的传输加密。核心关键词有两个——TLS,以及一套我自己总结的评估方法“双基准80”。如果你正在做RAG应用、智能客服、Agent类产品,或者只是在自己的项目里接了大模型API,这篇文章都值得你花十分钟看完。
1. “AI记忆”裸奔的真相:那些被忽视的明文点
1.1 一次抓包看得我坐不住了
我说的这个AI应用,是一个典型的智能客服Demo,架构并不复杂:浏览器页面走网关,网关后面是智能体服务,智能体再调用外部模型API和本地向量知识库。做安全评审的时候,我习惯先在网关出口做一个被动流量镜像,看看实际通信到底长什么样。
结果让我汗颜。通过WebSocket传输的对话消息里,JSON字段清晰可见:用户问了什么、系统回了什么、RAG检索出来哪几段文档,全部明文躺在那。更夸张的是,连系统提示词的完整文本也在里面。
这里所谓的“AI记忆”,不只是数据库里的历史记录,还包括当前对话上下文中瞬时流动的那些数据——用户的原始问题、被召回的知识片段、对话摘要、用户画像标签。这些数据构成了AI服务的“记忆”,如果它们在传输过程中没有加密保护,任何能触达网络链路的人都能把它们捞走。
1.2 为什么大家都觉得“内网很安全”
很多开发者的第一反应是:“这服务跑在内网,外网访问不到,应该没事吧?”
这个想法我太熟悉了,因为我自己也这么想过。但实际拆开看,有三个误区:
第一个误区,内网不等于只有自己人。一个稍微大一点的公司,内网里会有几十上百个业务系统,权限管理不一定严格。有人连上了办公Wi-Fi,就能扫到内部端口。很多AI服务还把接口监听在0.0.0.0上,没有任何访问控制。
第二个误区,内网流量没人会看。内部网络存在太多被动采集流量的方式:运维监控系统、日志平台、网络交换机镜像端口、软交换设备插件,甚至某个调试排障的同事在链路上抓包。你可以对着自家网络拓扑数一数,能“看到”流量经过的节点有多少个。这就好比合租公寓,每个房间都有门锁,但走廊上装了摄像头,谁路过、在哪个门口停留,其实都被记录下来。
第三个误区,一个地址加上了网关就等于加密了。很多网关只负责路由和鉴权,源站之间的通信仍然是明文HTTP。这就像大厦门口有保安,但每层楼之间的通道完全不设门禁,包裹在楼内怎么搬运,没人管。
1.3 裸奔的代价:截获、篡改和信任崩塌
明文传输的直接风险是数据被截获。用户的对话内容一旦被第三方拿到,轻则隐私泄露,重则被用来做社工攻击。对面向C端的AI应用来说,这基本等于自毁信任——用户一旦发现自己的聊天记录被泄露,挽回的难度是灾难级的。
更隐蔽的风险是篡改。攻击者不仅可以“偷看”,还可以在流量里改包:把系统提示词改掉,往被检索的知识片段里插入恶意内容,让AI在回答问题时输出一段钓鱼链接。这已经不是“记忆被偷看”,而是“记忆被篡改”。AI的答案依赖上下文,上下文一旦被人动过手脚,模型输出的可信度归零。
另外还有合规审计。现在面向用户的数据保护审计中,“传输加密”几乎是默认的检查项。你的AI服务如果还在大量使用明文通信,审计报告上大概率会被直接判为不通过。即使你暂时没有外部合规压力,我也建议从今天开始把加密当成默认选项。
2. TLS到底保护了什么:AI场景下必须懂的传输加密基础
2.1 HTTP和HTTPS:不只是多个字母
要理解TLS,先明白它解决什么问题。TLS位于传输层和应用层之间,核心做三件事:加密、认证、完整性校验。
用一个快递类比:明文HTTP是敞口的信封,谁拿到都能看;TLS是带锁的保险箱,只有收发双方有钥匙。更关键的是,TLS还有一个“防拆封条”——数据只要被改写,接收方就能发现。
TLS握手过程其实也不复杂:客户端先发起请求,服务器返回证书和密钥交换参数,客户端验证证书,双方协商出一个临时会话密钥,之后所有数据都用这把临时密钥加密传输。握手阶段使用非对称加密来安全交换密钥,数据传输阶段使用对称加密,因为对称加密快得多,适合高吞吐场景。
这个设计很像生活中的门禁系统:先用门禁卡验证身份进入大楼,然后在房间里用一把临时锁锁住私人文件柜,会话结束锁就报废,下次再发一把新的。
2.2 TLS版本选型:1.2还是1.3
TLS版本选择直接影响安全性和握手性能。我直接放一张对照表:
| 版本 | 发布时间 | 握手往返次数 | 安全状态 | AI场景建议 |
|---|---|---|---|---|
| TLS 1.0 | 1999年 | 2次RTT | 已过时,存在大量公开漏洞 | 禁用 |
| TLS 1.1 | 2006年 | 2次RTT | 已过时 | 禁用 |
| TLS 1.2 | 2008年 | 2次RTT | 当前主流,需配合强加密套件 | 启用 |
| TLS 1.3 | 2018年 | 1次RTT | 当前推荐,弱套件被强制移除 | 优先启用 |
对AI应用来说,TLS 1.3还有一个额外价值:握手少一次往返。智能体对话场景里,客户端和服务端之间会频繁建立连接,每次连接少一个RTT,累积下来对用户体感有明显提升。流式输出场景的感知尤其明显,首字延迟会降低。
如果你问我具体怎么配置:对外服务的网关,只开TLS 1.2和TLS 1.3;老设备兼容性实在有要求的,最低也只允许到1.2,低于这个版本的客户端直接拒绝连接。因为TLS 1.0和1.1的公开漏洞已经多到数不清,留着的风险远大于收益。把它想象成小区门口的老式门锁,看着能用,实际上早就被人研究透了。
2.3 密码套件:别把加密做成“假加密”
选对TLS版本还不够,密码套件的配置同样重要。密码套件可以简单理解为“用什么算法组合完成加密和认证”,它决定了加密强度和数据传输效率。
我的建议是直接看几个关键字:
密钥交换选ECDHE,它能提供前向保密——即使服务器的私钥将来泄露,之前的会话记录也无法被解密。不要用静态RSA密钥交换,那是拿一把永久的钥匙锁门,丢了等于全完。
对称加密优先选AES-GCM,或者ChaCha20-Poly1305。AES-GCM在支持AES-NI指令集的现代CPU上有硬件加速,性能损耗几乎可以忽略;ChaCha20-Poly1305在移动设备这类没有硬件加速的环境下表现更好。
需要禁用的包括RC4、3DES,以及CBC模式的AES。CBC模式存在多个已知攻击面,修复成本也高,索性直接不用。
一个比较容易踩的坑是:有人为了“安全”,自己发明了一套复杂的套件排序,结果导致某些客户端连不上。我的做法是使用主流软件默认提供的“中间兼容”模板,在它的基础上删掉明确不安全的项。这样既有兼容性,也保证强度。TLS 1.3比TLS 1.2省心的地方在于,它已经在协议层面移除了弱套件,所以只要版本够新,套件配置翻车的概率天然降低。
2.4 证书:自签名、通配符与自动续期
TLS里的“认证”环节依赖证书。证书这块有三个高频问题。
第一,自签名证书是个坑。自签名证书用于本地测试没问题,但很多人把它带到生产环境,为了绕过客户端的证书校验,直接写代码关闭verify。这一关,TLS的“加密”还在,但“认证”没了。中间人可以自己生成一张自签名证书来冒充你的服务端,加密照样能建立,只是解密方变成了攻击者。正确做法是使用受信任CA签发的证书,或者自建私有CA并让所有内部服务信任这根CA根证书。
第二,通配符证书要慎用。一张*.example.com的证书确实方便,但私钥一旦泄露,所有子域名的加密信任全部失效。我的习惯是按服务模块拆分证书,至少关键服务要用独立证书。如果证书数量多了,用SAN证书把几个域名合并在一张证书里,比通配符安全得多。
第三,证书续期必须自动化。现在公共CA签发的证书有效期普遍较短,到期不更新,AI服务的API就会突然大量报SSL错误。对此可以用ACME协议做自动续期,内部私有CA虽然没有标准ACME服务,但也要建立证书到期监控和提醒机制。千万不要等到用户反馈“连不上了”才去检查证书,那说明裸奔已经持续了一段时间。
3. “双基准80”评估法:把AI通信安全变成可量化指标
3.1 为什么需要“双基准”,80这个数字怎么定
安全团队和业务团队之间经常吵架:一方说“必须全面上TLS”,另一方说“加密会拖慢AI接口响应”。这种争论之所以僵持不下,是因为大家没有统一的数据标准。
后来我总结出一套评估方法:“双基准80”。所谓双基准,指安全基线和性能基线。两个维度都打分,80分是及格线。
80不是随便拍出来的。从安全角度看,评分低于80意味着至少存在一项明显的安全弱点,比如还在用TLS 1.0、启用了弱套件、证书链不完整等。从性能角度看,基于我自己的实测,TLS在AI应用这类请求链路里造成的端到端延迟损耗通常小于10%,超过20%基本说明配置有严重问题——比如握手缓存没开、连接没有复用、证书链冗余、套件选错。所以把损耗阈值定在20%,对应得分80分,低于它就必须整改。
这套方法最大的价值在于:把“感觉不安全”和“感觉慢了”变成可沟通的得分。当双基准结果亮出来,团队很容易达成一致,没有人再凭感觉争论。
3.2 安全基准A:TLS配置强度评分细则
安全基准A是对TLS配置的静态检查评分,满分100分。检查项和权重如下:
| 检查项 | 权重 | 得分条件 |
|---|---|---|
| TLS协议版本 | 25 | 仅TLS 1.2及以上且启用TLS 1.3得满分;未启用TLS 1.3酌情扣分;存在TLS 1.0/1.1记0分 |
| 密码套件强度 | 20 | 使用ECDHE + AES-GCM或ChaCha20得满分;存在CBC/RC4/3DES记0分 |
| 证书信任链 | 20 | 受信任CA签发且证书链完整得满分;自签名或跳过校验记0分 |
| 证书有效期 | 10 | 剩余有效期≥30天得满分;不足30天扣分;已过期记0分 |
| 前向保密 | 10 | 支持ECDHE得满分;仅RSA密钥交换得0分 |
| OCSP Stapling | 10 | 已开启且校验通过得满分 |
| HSTS | 5 | 已配置得满分;纯后端服务间调用可豁免 |
综合分低于80,说明至少有一个方面存在明显问题。这时候不要想着“分数差不多就行”,直接按检查项逐个整改。
快速自检可以用两条命令:
openssl s_client -connect host:443 -tls1_3 </dev/null 2>/dev/null | grep "Protocol" openssl s_client -connect host:443 </dev/null 2>/dev/null | grep "Cipher is"第一条看TLS 1.3是否握手成功,第二条看协商出的加密套件。如果第一条没有输出、第二条显示的是CBC或3DES,那你基本可以确定安全评分上不了80。
3.3 性能基准B:加密对AI业务体验的影响
安全评分过了,还要看性能。性能基准B的测试逻辑不复杂:
- 先测明文HTTP基线的P95延迟和吞吐量;
- 再测开启TLS之后相同业务的P95延迟和吞吐量;
- 计算损耗率:延迟损耗率 = (加密延迟 - 明文延迟) / 明文延迟,吞吐损耗率类似。
评分规则我列成一张表:
| 损耗率 | 性能得分 | 结论 |
|---|---|---|
| ≤5% | 100 | 优秀,无需优化 |
| 5%~10% | 90 | 良好 |
| 10%~20% | 80 | 及格线,建议优化 |
| >20% | 低于80 | 必须整改 |
测试的时候要覆盖两种流量:一是普通API请求,比如鉴权、取历史记录;二是流式对话,包括SSE和WebSocket。AI应用的流式对话对延迟更敏感,如果只测单张静态页面,数据不具有代表性。压测并发数要贴近生产实际,我一般用50并发跑3分钟以上,不然误差太大。
关于性能的常见误解:大家都觉得“加密一定变慢”,但实际上现代硬件加上AES-GCM,加密开销很低。高损耗多半出在握手环节:每次连接都重新握手、客户端没有长连接池、服务端没开session复用,这些才是罪魁祸首。
3.4 双基准如何联动判定与落地
双基准80的判定逻辑需要一套联动规则:两项都≥80视为通过;任意一项低于80则触发整改。整改完再跑一轮,直到两项都达标。
这套方法要落地,不能只在评审时跑一次。我的做法是把它嵌入日常开发流程:
- 网关、证书或TLS配置变更时,在预发环境自动跑双基准脚本;
- 静态安全扫描可以写成脚本,接入CI流水线,每次推送自动执行;
- 每周用定时任务检查证书剩余有效期,提前发现到期风险;
- 每月做一次完整回归压测,防止性能回退。
一旦跑起来,你会发现“加密影响性能”这句话会慢慢从字典里消失,因为实测数据就摆在那:安全评分95,性能损耗7%,两者都在80分以上,还有什么可吵的?
4. 实战改造:一个智能客服Demo的TLS加固全记录
4.1 改造前:一张问题清单触目惊心
我前段时间帮一个内部项目“小X助手”做TLS加固,它是一个典型的智能客服Demo,架构是:浏览器页面 → API网关(Nginx)→ 智能体服务(Python)→ 外部模型API + 本地向量知识库。
改造前我用双基准的检查思路做了一次扫描,结果列出来是这样的:
| 问题项 | 现状 | 风险级别 |
|---|---|---|
| TLS版本 | 仅启用了TLS 1.0和1.1 | 高危 |
| 证书 | 自签名证书,客户端代码关闭了证书校验 | 高危 |
| WebSocket | 使用ws://明文协议,对话内容完全可见 | 高危 |
| 服务间调用 | 智能体到向量库走明文HTTP | 中高 |
| 日志 | 日志文件记录了完整的prompt文本 | 中 |
| HSTS | 未开启 | 中 |
TLS 1.0和1.1的漏洞早已是公开话题,自签名证书加上关闭校验意味着整个加密形同虚设——我甚至可以说,改造前的这台服务基本等于什么都不穿站在大街上。日志里记录prompt也是隐患,但这部分属于日志脱敏范畴,单独处理,不在TLS这一篇展开。
4.2 网关层协议与套件修正
第一步是修正网关配置。改完后的Nginx相关配置长这样:
server { listen 443 ssl http2; server_name ai-gateway.internal; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets on; ssl_certificate /etc/ssl/certs/ai-gateway.pem; ssl_certificate_key /etc/ssl/private/ai-gateway-key.pem; ssl_trusted_certificate /etc/ssl/certs/chain.pem; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; ssl_stapling on; ssl_stapling_verify on; }逐个说下关键参数:
ssl_protocols只保留TLSv1.2和TLSv1.3,把老版本全部挡在外面。ssl_ciphers只留下ECDHE搭配AES-GCM和ChaCha20的组合,保证前向保密和AEAD加密。ssl_prefer_server_ciphers off让现代客户端优先选择服务端套件顺序,同时不会强制老旧客户端使用不兼容的套件。ssl_session_cache、ssl_session_timeout和ssl_session_tickets一起开,是为了会话复用。这是性能基准能不能到80的关键,没有这一步,每次请求都完整握手,延迟损耗大概率超过20%。
HSTS和OCSP Stapling也一起开了。HSTS让浏览器强制走HTTPS,防止协议降级攻击;OCSP Stapling把证书状态查询从客户端负担变成服务端主动推送,减少一次往返。
4.3 服务间调用与WebSocket的加密适配
网关改完后,还要解决两个“半裸奔”的点。
第一个是服务间调用。很多架构只加密了前端到网关这一段,网关到后端AI服务仍然是明文HTTP回源,这等于只有大门装了锁,楼内依然畅通无阻。我对智能体到向量库的调用做了mTLS改造,双向证书认证,不仅客户端验证服务端身份,服务端也验证客户端身份。Python端的调用方式类似下面这样:
import httpx client = httpx.Client( base_url="https://vector-db.internal:8443", cert=("/etc/ssl/client.pem", "/etc/ssl/client-key.pem"), verify="/etc/ssl/ca-chain.pem", )cert传客户端证书和私钥,用于证明自己的身份;verify传CA链,用来校验服务端。mTLS特别适合AI服务之间的调用,因为智能体节点一旦被仿冒,攻击者可能会直接去向量库拉取全部知识库数据,双向认证能有效阻断这种伪造。
第二个是WebSocket。前端的连接地址要从ws://改成wss://,网关侧的代理规则也要跟着调整:
location /ws { proxy_pass https://ai-service.internal:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300s; proxy_buffering off; }有一个细节值得单独拎出来说:proxy_buffering off是AI流式输出场景里的关键配置。如果开着缓冲,前端会经历“长时间没有反应,最后一次性吐出一大段文字”的诡异现象。关掉缓冲,增量内容才能实时流到前端,保持打字机效果。
4.4 改造后的双基准结果
改造完成之后,我重新跑了一遍双基准评估,结果是这样的:
| 指标 | 改造前 | 改造后 | 说明 |
|---|---|---|---|
| 安全评分 | 35 | 95 | 扣分项主要是证书剩余有效期只剩28天 |
| P95端到端延迟 | 320ms | 342ms | 损耗约6.9%,性能评分90 |
| WebSocket明文流量 | 存在 | 全部WSS | 无明文对话 |
| 服务间明文调用 | 存在 | 全部mTLS | 双向身份认证 |
安全评分从35分拉到95分,性能损耗控制在7%左右,两个维度都超过了80分线。这个结果验证了一个判断:TLS不是性能杀手,配置不当才是。连接复用好、套件选对、证书链精简之后,加密带来的额外开销对用户几乎无感。
5. 常见问题与排查技巧实录
5.1 开了TLS之后AI接口变慢,先别急着骂加密
很多人改造完TLS的第一个反应是“接口怎么变慢了”。我的建议是,先别急着归罪加密,按这个顺序排查:
- 确认是不是每次请求都在重新握手。用同一个客户端连续请求两次,如果第一次明显慢、第二次快,那问题就在连接复用上。客户端开连接池、启用keep-alive,服务端开session cache和session tickets。
- 检查密码套件。如果套件选的是老的CBC模式,又没有硬件加速,性能确实会差;换成AES-GCM或ChaCha20之后通常立刻改善。
- 检查证书链。证书链太长会导致握手包变大,延长协商时间。把多余的中间证书清理掉,只保留必要的链。
- 检查OCSP。如果开启OCSP查询但没开Stapling,每次连接都要额外查一次证书状态,延迟增加明显。
我在实测中遇到过一次性能损耗高达30%的情况,最后发现原因就是客户端每次请求新建连接、服务端没有开session复用。修完之后损耗直接降到7%。所以遇到变慢,第一个怀疑对象永远是握手,而不是加密算法本身。
5.2 流式输出遇到WebSocket,怎么加密才不断流
AI应用非常依赖WebSocket和SSE这类流式通道做逐字输出。加密改造时有三件事容易踩坑:
一是协议升级必须彻底。浏览器从HTTPS页面发起ws://连接会被混合内容策略直接拦截,所以必须用wss://。如果前端页面已经部署在HTTPS下,连接地址里的协议忘了改,页面会报错,这是最常见的低级问题。
二是代理的超时设置。智能体生成一段长回答可能要几十秒甚至更久,网关上的proxy_read_timeout默认值往往不够,连接中途被断开,前端直接把错误抛给用户。这个值要调大,同时给WebSocket设计合理的心跳机制。
三是缓冲问题,这个前面已经提过。proxy_buffering off必须配上,否则流式输出会变成“整包发送”,完全失去逐字效果。
排查时打开浏览器开发者工具,看网络面板里的WS连接是否显示加密标记,同时观察是否出现大量报文积压。如果一切正常,流式体验和明文时代没有肉眼可见的差别。
5.3 内网、本地联调,到底要不要上TLS
有人说:“本地开发环境搞TLS太麻烦,反正也没人看。”我的建议是:本地可以灵活一点,但团队共享的测试环境必须和生产保持一致。
本地开发的话,用自签名证书加本地信任CA就好,不要关闭校验。Workflow里可以加一个启动脚本,自动生成证书并安装到本机信任区。这样开发环境虽然证书是自签的,但校验逻辑没有退化。
团队共享的测试环境就严格多了,必须使用与生产相同的证书方案,至少同一套私有CA或同款公共CA签发的证书。否则经常会出现“测试环境一遍过、上线环境全线崩”的情况。从我的经验看,内网明文流量被内部日志系统全文收录、然后被权限过宽的团队查到的事件并不少见。你不想用户某句敏感提问出现在别人的日志检索结果里吧?那就在内网也把TLS开起来。
5.4 证书过期导致智能体集体“失忆”的教训
证书过期是我见过的最典型的“静默事故”。某一天,智能体的所有API突然开始报certificate verify failed,排查半天才发现证书在前一天夜里过期了。
这类问题最好的解法是预防。ACME自动续期解决公共证书的续期问题,内部私有CA也要配置到期提醒。我在服务端放了一个每周巡检脚本,用一条openssl命令就能看到证书状态:
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -noout -dates注意检查时要覆盖所有节点。我遇到过一次场景:网关证书换了,但源站证书没换,结果用户流量被网关TLS终结后,回源时源站证书过期,导致部分功能瘫痪。所以证书巡检必须包含“从客户端视角”和“从源站视角”两条链路,不能只看一处。
还有一个细节:负载均衡器上的证书和源站证书要一起检查。很多团队只换其中一个,留下了半个坑,下次事故换个姿势再来一次。
个人经验来说,TLS安全加固这件事,听起来琐碎枯燥,但它是对AI应用长期稳定运行回报最高的一项投入。我现在已经习惯把“双基准80”写进发版前的固定动作:先跑安全基线扫描,再跑一轮真实对话链路压测,两项都过80才允许打tag。最后分享一个小技巧:如果你怀疑某个AI服务的数据链路又回到了裸奔状态,直接在网关节点抓一把流量,搜一下用户最近问过的那句话——如果能在明文里搜到,那说明问题又回来了。做到这个程度再回头看当初的配置,你会庆幸自己多花了那一下午。