HTTPS 的锁是怎么挂上的?TLS 1.3 握手 6 步实测

地址栏那把锁,背后只谈了 6 句话。

我在浏览器里把 TLS 1.3 握手拆成 6 步,一步一步跑完:50ms 延迟下总耗时 63.6ms,密码学计算只占 2.6ms,双方最终拿到同一把会话密钥。

把其中一条消息里的公钥换掉,第 5 步立刻报 OperationError,握手停住。

在线案例与源码:gitcode.com/OPCTeam/dem... 成品只有一个 index.html,双击就能打开,不连网、不装依赖。

一、先看结果:6 步、1004 字节、63.6 毫秒

这 6 步不是画出来的流程图,每一步都真的生成了随机的密钥和报文。

步骤 消息 实测耗时 字节数
1 ClientHello 0.2 ms 254 B
2 ServerHello 0.3 ms 254 B
3 EncryptedExtensions + Certificate 1.3 ms 182 B
4 CertificateVerify + Finished 0.5 ms 140 B
5 客户端 Finished 0.7 ms 76 B
6 应用数据 0.4 ms 98 B

6 步加起来 1004 字节。

页面顶部的 RTT 滑块放在 50ms,总耗时读数 63.6ms。

50ms 是模拟的一次往返,2.6ms 是 ECDH、HKDF、AES-GCM、ECDSA 四种运算加起来的真实耗时,剩下约 11ms 是 setTimeout 和页面渲染的固定开销。

这三个数对不上才奇怪,因为浏览器里的计时器本来就有几毫秒的粒度。

二、为什么值得看:能跑的文字教程太少

搜"TLS 1.3 握手",出来的大多是时序图和一大段名词解释。

图能看懂,但没人告诉你 ClientHello 到底装了多少字节,也没人让你亲手把 key_share 换掉看它怎么失败。

我把这件事做成了一个单页应用,规则很简单:页面上出现的每一个十六进制字符串,都必须由 WebCrypto 当场算出来。

不是预埋的假数据,刷新一次,公钥和共享密钥全变。

这就是它和普通演示页的区别。

三、密钥到底怎么换:ECDH 只负责一半

第一步,客户端和服务端各生成一对 ECDH P-256 密钥。

双方交换公钥,各自用私钥做一次 deriveBits,得到 32 字节的共享秘密。

页面里两行十六进制必须一模一样,旁边会亮一个"一致"的绿标。

但共享秘密不能直接拿去当 AES 密钥。

TLS 1.3 中间还夹了一层 HKDF-SHA256:把共享秘密、双方随机数和固定的 info 字符串喂进去,派生出四条方向的密钥。

  • 客户端→服务端 Handshake Key
  • 服务端→客户端 Handshake Key
  • 客户端→服务端 Application Key
  • 服务端→客户端 Application Key

每条 16 字节,对应 AES-128-GCM。

为什么分方向?因为两端用的密钥必须错开,否则一方加密、另一方拿同一把钥匙解密,重放和反射攻击就有了空间。

第三步是证书链。

页面现场生成 Root CA、Intermediate CA 和站点证书三对 ECDSA P-256 密钥。

Root 自签名,Intermediate 由 Root 签,站点证书由 Intermediate 签。

验签也是真的:用上一级公钥去 verify 下一级的签名,三条全过,面板显示 3/3。

四、验证与踩坑:两个真 bug

第一版跑起来时,前四步都绿,第 5 步直接红。

日志只有一行:服务端 Finished 解密失败,OperationError。

原因不复杂:客户端拿自己的 Handshake Key 去解服务端加密的数据。

TLS 1.3 的双向密钥是分开的,客户端加密用 c2s,解密要用 s2c。

第一版只派生了两把钥匙,方向一错,AES-GCM 的认证标签必然对不上。

修法是把四条密钥都派生出来,加密用自己方向的那条,解密用对方方向的那条。

第二版改完,握手跑通。

第二个坑更隐蔽。

自检里有一条"篡改签名后验签失败",结果它连原签名都判失败。

排查后发现,代码里写的是 new Uint8Array(sig)。

这不是复制,是视图。

后面的 tampered[flipPos] ^= 0xFF 改的是同一块内存,原签名跟着一起坏了。

改成 sig.slice(0) 先复制一份,再翻转字节,原签名通过、篡改签名失败,断言才真正成立。

这两个 bug 都不是排版问题,是密码学代码里最容易骗过眼睛的那种:界面看起来正常,逻辑已经反了。

改完后页面内 9 条自检全过:共享密钥一致、HKDF 两端一致、证书链 3/3、篡改验签失败、每次 ECDH 密钥不同、AES-GCM 往返恢复原文、篡改密文认证失败、正常签名通过、不同共享密钥派生结果不同。

五、三种模式的账:1 RTT、2 RTT、0 RTT

页面右上角可以切三种模式,RTT 滑块从 10ms 到 200ms。

同一个页面、同一套密码学运算,只改往返次数。

模式 RTT 次数 50ms 下总耗时 150ms 下总耗时 计算耗时(50ms 档)
TLS 1.3 1 63.6 ms 160.8 ms 2.6 ms
TLS 1.2 2 113.4 ms 313.1 ms 2.8 ms
会话复用 0 10.1 ms 10.4 ms 2.2 ms

50ms 这一档,TLS 1.3 比 TLS 1.2 少 49.8ms,差不多正好省下一次往返。

150ms 这一档差距拉到 152.3ms,因为往返越贵,少一次往返越值钱。

0-RTT 那栏看着最夸张,10ms 出头,但它量的是复用连接后的数据阶段,不是第一次访问。

这里有个边界要说清楚:三种模式共用同一套密钥交换和证书链演示,所以"握手总字节"都是 1004 字节。

真实的 0-RTT 不会重做证书链,字节数会小得多;页面里的 1004 是演示实际构造的报文总量,不是线上 0-RTT 的握手大小。

六、失败路径:换掉公钥,握手就断

点"中间人攻击",再点"开始握手",页面会模拟攻击者替换 ServerHello 里的 key_share。

客户端用自己的私钥和攻击者的公钥算,服务端用另一套,两个共享秘密当场对不上。

第 5 步解密服务端 Finished 时抛 OperationError,时间轴变红,状态栏写"握手失败 --- 中间人攻击",总耗时停在 61.4ms。

再点"篡改密文",页面会翻转应用数据密文中间的一个字节。

这次是第 28 字节,0x16 改成 0xe9。

AES-GCM 解密直接报认证失败,第 6 步标红,页面提示"篡改检测成功 --- AES-GCM 认证失败"。

一个断在密钥协商,一个断在数据完整性,两条失败路径都能复现。

七、准备环境:进入码道 Web 与本地复现

下载仓库里的 index.html,双击打开即可。

本地复跑 9/9 通过,9 条断言全绿。

也可以在当前目录起一个静态服务:python -m http.server 8000,再打开 http://localhost:8000/index.html。

不需要 npm,也不会发出任何外部网络请求。

按这个顺序点,能复现本文所有数字:

  1. RTT 滑块保持 50ms,点"开始握手",看 6 步耗时和总耗时 63.6ms。
  2. 切到"指标对比",依次跑 TLS 1.2 和会话复用,看三行对比。
  3. 点"运行自检",9 条断言应全绿。
  4. 点"中间人攻击"再点"开始握手",第 5 步应报 OperationError。
  5. 点"篡改密文"再点"开始握手",第 6 步应报 AES-GCM 认证失败。

浏览器用 Chrome 或 Edge 90 以上都行,WebCrypto 的 ECDH、HKDF、AES-GCM、ECDSA 都是原生支持。

想改需求重新生成,可以打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source...

八、它不是什么

这不是一个 TLS 实现,只是一间把协议拆开看的实验室。

页面没有真实网络、没有 TCP、没有抓包,也没有解析真实的 X.509 证书;证书对象是页面按字段构造的,签名和验签用的是真实 ECDSA。

RTT 是 setTimeout 模拟的,不是线上测量值。

CPU 调度和垃圾回收也会影响读数,所以同一档位多跑几次,总耗时会有几毫秒浮动。

0-RTT 只做了时间账,没有实现会话票据和 PSK 复用。

把它当成理解握手顺序和密钥方向的教具可以,别拿它当安全评估工具。

九、使用码道体会

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

需求里我把"必须用 WebCrypto 真算"写死了,生成出来的第一版确实用了真的 ECDH 和 AES-GCM,但密钥方向错了。

有意思的是,它自己跑测试时发现了这个错,日志里写着"客户端用 clientHandshakeKey 解密服务端数据,key 不匹配必然失败",然后整份重写。

第二个视图和副本的坑,也是它自己定位到 new Uint8Array(sig) 这一行。

我要做的不是替它写代码,而是把验收条件写清楚:哪些数必须一致,哪条路径必须失败,失败时页面要显示什么。

条件写清楚了,返工就少。

十、下一步

这份页面还缺三块:TLS 1.2 的真实报文结构、会话票据的完整复用流程、以及从 pcap 文件导入一次真实握手做对照。

把这三块补上,它就从"教具"更接近"观察工具"。

如果你也在做类似的协议可视化,欢迎拿这份单文件改,仓库地址在文章开头。

相关推荐
程序员大阳19 分钟前
副队长ES6+教程(1)--ES6+初识
前端·ecmascript·es6
码云社区34 分钟前
智慧物业系统选型指南:房屋档案、账单管理与报修协作
前端·javascript·vue.js·智慧物业
deli0072 小时前
2GB 文件传一半断网实测:HTTP 断点续传只补 15MB
前端
treasure14013 小时前
【复古】浅尝Django前后端不分离
前端·css·python·django·bootstrap·html·web
jay神14 小时前
基于 YOLOv8 的手势识别系统(Web 版)
前端·人工智能·python·深度学习·yolo·计算机视觉·计算机毕业设计
屏什么录14 小时前
长时间录屏中途崩溃:MP4 打不开的 6 种救回方案与参数设置
前端·音视频开发
code_Bo14 小时前
不只命令行:我把跨 Agent 会话同步做成了桌面应用
前端·javascript·后端
kyriewen14 小时前
AI 能把页面写出来,为什么改个需求就开始崩?
前端·javascript·人工智能
涛涛ing15 小时前
2026年最值得关注的前端变化,藏在“默认值”里
前端