十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SSL/TLS握手全解析:从TLS 1.2到1.3的流程、密钥交换与安全加固

SSL/TLS握手全解析:从TLS 1.2到1.3的流程、密钥交换与安全加固 我把SSL/TLS握手这件事从头到尾重新整理了一遍。这个题目被问烂了但真正能讲清楚的人不多——不是说背不出那几步而是很多人不知道“为什么是这几步”。本文从握手要解决的问题、TLS 1.2 和 TLS 1.3 的流程差异、RSA 和 ECDHE 的本质分歧、Wireshark 抓包验证到 CVE-2016-2183 这类衍生安全问题做了一个完整梳理适合准备面试的技术人员、刚接触 HTTPS 的开发者以及想弄明白“握手失败到底怎么排查”的运维同学。1. 握手不是形式主义它要在一轮网络往返内解决四个信任问题很多教材把握手讲成一张流程图ClientHello、ServerHello、证书交换、密钥协商、Finished。背下来不难但面试官继续追问一句“为什么要有 ChangeCipherSpec”或者“为什么 TLS 1.3 把它删了”很多人就愣了。原因很简单——你是按流程记的不是按问题记的。如果把握手理解成“解决沟通前的四个信任障碍”整个流程不但记住了还能自己推演出来。1.1 四个问题你是谁、你的话可信吗、密钥怎么给、消息有没有被改两个之前没有任何共享信息的实体要通过一个明文可见、可被窃听、可被篡改的网络上建立安全通信必须依次解决四件事身份认证客户端如何确认服务器是它声称的那个服务器而不是中间人伪造的。这是证书链要解决的事。不可否认的协商可信度服务器证明自己确实持有与证书对应的私钥否则任何人都可以拿一份公开的证书来伪装。这一步通常通过签名来体现。密钥配送双方需要生成一个第三方无法得知的对称加密密钥。但网络上不能直接传密钥所以要用非对称加密或 Diffie-Hellman 类算法来“间接”让双方各自算出一个相同的密钥而传输过程中不泄露这个密钥本身。完整性校验握手过程中的所有参数有没有被中途篡改双方各自计算摘要并在 Finished 消息中验证。这四个问题对应到 TCP 之上就是一次完整握手的骨架。技术细节各有差异但目标不变。理解了目标再去看每一步——ClientHello 里为什么带随机数、证书里为什么包含公钥、为什么还要单独做一次密钥交换就不会觉得是“规定”了。1.2 一把对称密钥用非对称方式“护送”过去一个初学者最容易卡住的概念是既然最终通信用的是对称加密为什么握手阶段要搞一堆非对称的东西答案很简单对称加密的密钥不能被明文传。网络上的任何消息都可能被监听。如果服务端在握手时直接把一个 AES 密钥发给客户端攻击者截获之后就能解密后面的所有流量加密形同虚设。所以握手的核心任务变成了在一条不安全的信道上安全地把一个对称密钥分发给双方。这个目标只有两种主流路径——第一用服务器公钥加密密钥材料送过去RSA 密钥交换第二双方各自生成私密参数只交换公开参数最后计算结果一致而且这个结果无法从公开参数反推ECDHE 密钥交换。这把对称密钥在 TLS 语境里叫Pre-Master Secret后续所有会话密钥都是由它配合两个随机数派生出来的。后面我会专门展开这两种方式的差异因为这是面试里最容易追问的区分点。2. TLS 1.2 完整握手一步一个报文每个报文都有存在的理由TLS 1.2 是目前兼容性最好、资料最多的版本也是理解 TLS 的根本。以最常见的 ECDHE_RSA 套件为例一次完整握手会产生多次 TCP 段交换我把它们拆开逐段解释。这一节值得反复看因为之后讲 TLS 1.3 和抓包全部建立在此基础上。2.1 ClientHello客户端亮出能力清单和一次性的随机数握手第一枪是客户端发出 ClientHello核心字段有三个客户端随机数Client Random32 字节的随机值后续生成主密钥Master Secret时会用到。注意这个随机数必须是明文发送的因为服务端需要它而且就算被监听也不怕——它只作为派生参数的一部分不是密钥本身。支持的密码套件列表比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256表示客户端愿意使用 ECDHE 做密钥交换、RSA 做身份认证、AES-128-GCM 做对称加密、SHA256 做摘要。支持的 TLS 版本列表客户端会列出自己能接受的最高版本。另外通常还会带上SNIServer Name Indication扩展告诉服务器“我要访问的是哪个域名”。这个字段很重要——如果 Nginx 上配了多个证书没有 SNI 服务器就没法决定返回哪张证书。早期抓包时经常看到客户端明明访问的是 A 域名服务器却返回 B 证书十有八九是 SNI 没传。2.2 ServerHello 与 Certificate服务端决定算法并亮明身份收到 ClientHello 后服务端做两件事一是从客户端列出的套件里挑一个自己支持的通过ServerHello返回选中的版本、随机数Server Random、会话 ID、密码套件。这里有个细节协商结果一定在客户端提供的列表之内如果服务端只支持高版本算法而客户端不支持双方无法继续会直接握手失败或降级处理。二是通过Certificate消息发送服务器证书。证书里有服务器公钥、证书链、有效期、签名等。客户端收到后要验证证书链是否完整、是否由可信 CA 签发、域名是否匹配、是否过期。这一步很多人以为只是“看一眼”实际上它是一个完整的 X.509 验证流程。在 ECDHE 套件下服务端还会紧接着发送ServerKeyExchange消息内容包含椭圆曲线参数、服务端临时公钥ephemeral public key、以及用自己私钥对以上参数的签名。这个签名的意义重大——它向客户端证明“持有证书私钥的人确实参与了这个握手”防止中间人把服务端公钥替换成自己的。如果套件是 RSA 密钥交换则没有 ServerKeyExchange因为公钥已经在证书里了不需要另外生成临时参数。最后服务端发送ServerHelloDone表示“我这边的握手信息发完了该你了”。2.3 ClientKeyExchange客户端把“共同密钥的种子”安全送达这是整个握手最关键的一步但不同套件做的事情不一样RSA 密钥交换客户端生成一个 48 字节的 Pre-Master Secret前 2 字节是协议版本号后 46 字节是随机数用服务器证书里的公钥加密后发给服务器。服务器用私钥解密。到这里双方都拿到了同一个 Pre-Master Secret。ECDHE 密钥交换客户端根据 ServerKeyExchange 里的曲线参数生成自己的临时密钥对把客户端公钥通过 ClientKeyExchange 发给服务器。随后客户端用“自己的私钥 服务端临时公钥”计算出一个共享点再通过 KDF 派生出 Pre-Master Secret。服务器用“自己的私钥 客户端公钥”算出同一个共享点。这个共享点本身从没在网络上传输过只是双方各自算出来的。通信双方此前已经各自持有 Client Random 和 Server Random现在再加上 Pre-Master Secret就可以通过 PRF伪随机函数派生出主密钥Master Secret再进一步派生出对称加密的会话密钥、MAC 密钥和 IV。这是对称加密真正使用的密钥谁拿到它谁就能解密后面的通信。在 ECDHE 流程中客户端一般还会对自己的密钥交换参数签名吗这里有个常见的理解误区客户端在证书认证模式下不需要提供证书它的公钥不需要签名。之所以安全是因为攻击者即便替换了客户端的公钥也无法同时伪造服务端私钥最终双方算出的共享密钥不一致握手会在 Finished 阶段失败。2.4 ChangeCipherSpec 与 Finished密钥切换的“分水岭”按 TLS 1.2 规范客户端发送完 ClientKeyExchange 之后会先发送一条ChangeCipherSpec意思是“接下来我要改用协商好的密钥加密了”然后发送Finished消息内容是用协商好的密钥加密的一段握手消息摘要。服务端收到后做同样操作自己也发送 ChangeCipherSpec 和 Finished。Finished 消息解决了握手过程中“之前所有内容是否被篡改”的问题——如果中间人改过任何一个字段双方计算出的摘要不一致握手就会失败。从这之后应用数据才开始用对称密钥加密传输。为什么要有 ChangeCipherSpec 这个看起来多余的步骤因为它是一个明确的状态切换信号防止通信双方在“老密码套件”上继续输送数据导致歧义。TLS 1.3 把它删了因为新版本通过协商机制隐式完成了切换但 TLS 1.2 里它仍然是重要一环抓包时经常看到它。3. RSA 与 ECDHE 的分歧前向保密是面试官最爱追问的点上面提到了两种密钥交换方式的走向不同这里必须展开讲。因为它是握手过程中“算法层面”最核心的区别也是区分面试者是真懂还是背流程的试金石。3.1 两种 Pre-Master Secret 传递方式的本质差异RSA 密钥交换比较直观客户端生成 Pre-Master Secret用服务器公钥加密发过去。服务器私钥解密。理解成本低实现也简单。但它有一个致命弱点——一旦服务器私钥泄露攻击者可以解密历史上所有录制的加密流量。如果攻击者早在三年前就抓了你的握手包而今天你服务器的私钥泄露了他只要用私钥解密当年那个 ClientKeyExchange就能还原出当年的 Pre-Master Secret进而推算出当年的会话密钥把三年前的流量全部解开。ECDHE 不一样。它每次握手都会生成一个临时的私钥ephemeral key这个私钥用完即弃不落盘。即使服务器长期私钥泄露也只能解密握手时的签名无法还原出那次会话的临时私钥也就无法还原 Pre-Master Secret。这种特性叫前向保密Forward Secrecy。简单类比RSA 方式就像你用一个永远不换的保险柜锁送东西钥匙后来被偷了所有曾经放进这个柜子的东西全部失守ECDHE 方式则是每次送东西都换一个一次性锁钥匙用完直接销毁小偷就算把仓库翻了也拿不到以前的货物。正因为这个原因现代 TLS 配置基本都要求启用 ECDHE 套件且逐渐禁用纯 RSA 密钥交换套件。TLS 1.3 甚至直接移除了 RSA 密钥交换只保留 ECDHE 类。面试官听到你回答“RSA 会泄露历史流量ECDHE 能做到前向保密”基本就知道你是理解过的。3.2 算法套件命名的含义四个环节各司其职密码套件名字很长比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256可以拆成四段来读字段时间含义典型值TLS_协议族前缀TLSECDHE密钥交换算法ECDHE / RSA / DHERSA证书认证算法RSA / ECDSAAES_128_GCM对称加密算法和模式AES / CHACHA20SHA256伪随机函数和摘要算法SHA256 / SHA384这四个环节不是同一个算法也不一定绑定。比如 ECDHE 做密钥交换、RSA 做证书签名、AES-GCM 做数据加密、SHA256 做完整性校验它们各管一段合起来组成一个套件。理解了这个结构面试时被问“为什么 TLS 1.3 只有 5 个套件”时也能顺着思路答出来——因为每个环节只保留最安全的选项不再提供冗长的兼容列表。4. TLS 1.3 把握手压缩成一次往返背后是取舍不是炫技TLS 1.3 最大的变化就是快。传统 TLS 1.2 需要两次 TCP 往返2-RTT才能开始发应用数据TLS 1.3 在标准握手下只需要一次往返1-RTT如果复用之前的会话甚至能做到 0-RTT。很多文章只提“更快”但面试官真正想知道的是它怎么做到的、付出了什么代价。4.1 把密钥协商提前到 ClientHello 里的代价与收益TLS 1.3 默认移除了静态 RSA 密钥交换只保留 ECDHE。这样一来客户端在第一次发送 ClientHello 时就可以直接附上自己猜测的密钥共享参数key_share服务器在 ServerHello 里直接返回自己的共享参数。两端在第一次往返结束后就已经算出了会话密钥紧跟着的 Certificate、Finished 全部在这个已经建立的加密通道里传输所以不用担心被窃听。代价是什么客户端必须在握手之前就猜测服务器会选用哪条椭圆曲线。猜错了怎么办服务器返回 HelloRetryRequest让客户端重新用正确的曲线再发一次 ClientHello这样又变回 2-RTT。实际情况中绝大多数服务器都支持 X25519所以猜测成功率很高。但在兼容老设备的环境里这个回退机制会带来额外的延迟。另外TLS 1.3 还把 ChangeCipherSpec 从协议流程里去掉了。这个变化背后有一个很现实的原因中间设备和防火墙长期根据 ChangeCipherSpec 来识别 TLS 流量如果 TLS 1.3 彻底不发这个字段很多企业防火墙会直接把流量拦截掉。所以实现上为了兼容性仍然可能发送空的 ChangeCipherSpec 记录dummy CCS但协议本身不依赖它做密钥切换。这就是“协议设计”与“现实网络设备”之间妥协的典型例子。4.2 0-RTT会话恢复的魔法与风险TLS 1.3 的会话恢复不再走 Session ID 或 Session Ticket 的方式而是客户端在 ClientHello 里直接携带一个PSKPre-Shared Key并用它加密第一批应用数据。服务器如果认可这个 PSK直接解出数据握手为零往返。这听起来很美好却有一个必须知道的风险——重放攻击。攻击者可以把这条 ClientHello 连同第一批数据原样重发一遍服务器没办法判断它是来自同一个客户端还是攻击者在抄录旧数据。所以 0-RTT 只能用在幂等请求上比如 GET 请求、查询类操作涉及修改状态或资金交易类操作绝不能启用 0-RTT。很多安全配置文档里明确建议关闭 0-RTT就是出于这个原因。4.3 版本协商机制的变化以前靠降级现在靠扩展TLS 1.2 时代如果客户端和服务器版本不一致通常会协商出一个双方都支持的旧版本。TLS 1.3 的版本协商方式也变了——客户端在 supported_versions 扩展里列出所有支持的版本服务器选一个写入 ServerHello而不是靠握手协议里的 version 字段去猜。为了阻止降级攻击服务器如果选择了低于 TLS 1.3 的版本会在 ServerHello 里写入一个特殊的随机值downlevel sentinel客户端发现后可以主动终止握手。这些细节虽然不一定当场考但面试官如果聊得深聊到“TLS 1.3 如何防止降级”你就知道这是答案的核心了。5. 用 Wireshark 抓一次真实握手理论再顺不如亲眼看一次报文讲再多流程都不如自己抓包看一次印象深。这一节用 Wireshark 和 OpenSSL 命令演示从理论到实战的完整链路顺便讲几个排查经验。5.1 抓包姿势什么场景最好抓握手测试最理想的环境是openssl s_client直连服务器因为你可以完全控制客户端行为而且 Wireshark 里一眼就能看到完整握手。先抓包sudo tcpdump -i ens33 -w /tmp/tls_handshake.pcap host 93.184.216.34 and port 443然后触发握手openssl s_client -connect www.example.com:443 -tls1_2 -msg-msg参数会在终端打印出所有握手消息的十六进制摘要配合 Wireshark 的图形界面两边对照看理解会直观很多。终端里你会看到类似 TLS 1.2 Handshake [length 00e8], ClientHello TLS 1.2 Handshake [length 004a], ServerHello TLS 1.2 Handshake [length 0b52], Certificate TLS 1.2 Handshake [length 010c], ServerKeyExchange TLS 1.2 Handshake [length 0004], ServerHelloDone TLS 1.2 Handshake [length 0066], ClientKeyExchange TLS 1.2 Handshake [length 0010], ClientKeyExchange ...5.2 Wireshark 过滤器打开抓包文件后在显示过滤器里输入tls.handshake.type 1 // ClientHello tls.handshake.type 2 // ServerHello tls.handshake.type 11 // Certificate tls.handshake.type 12 // ServerKeyExchange tls.handshake.type 14 // ServerHelloDone tls.handshake.type 16 // ClientKeyExchange tls.handshake.type 20 // FinishedWireshark 的 TLS 解析器里每一类握手消息都有数值编号。抓包时一个很容易犯的错是直接用ssl作为过滤关键字——新版本 Wireshark 里协议名称已经改为tls虽然ssl仍然兼容但搞清楚tls更符合当前术语习惯。展开 ClientHello 记录时要注意两个地方Random字段32 字节的客户端随机数后续派生会话密钥时用到。Extension: server_name如果这里看不到 SNI说明客户端没有传域名连接到 IP 直连场景时服务器可能返回默认证书。展开 Certificate 记录时重点看证书的 Signature Algorithm 和 Public Key Algorithm。如果服务器证书是 ECDSA 签的ServerKeyExchange 里的签名算法也会是 ECDSA二者需要匹配否则客户端会报错。5.3 用 OpenSSL 验证不同协议版本想快速对比 TLS 1.2 和 TLS 1.3 的差异可以分别跑openssl s_client -connect www.example.com:443 -tls1_2 -msg openssl s_client -connect www.example.com:443 -tls1_3 -msg在 TLS 1.3 抓包里你会看到 ClientHello 里多了一个key_share扩展里面直接带着客户端生成的 X25519 公钥。服务器在 ServerHello 里直接返回key_share响应后续的 EncryptedExtensions、Certificate、Finished 全部在加密状态下传输Wireshark 由于没有密钥显示出来的内容会是Encrypted Handshake Message。这就是 TLS 1.3“加密一切”与 TLS 1.2 的直观差别。如果想看解密后的 TLS 1.3 流量可以在 Wireshark 的 TLS 协议设置里配置(Pre)-Master-Secret log文件然后在客户端设置环境变量export SSLKEYLOGFILE/tmp/tls_keys.log curl -v https://www.example.com/Wireshark 指定这个日志文件后就能看到解密后的 HTTP/2 请求头。这个方法在调试 HTTP 请求、排查加密层问题时非常实用我基本每次排查都开着。6. 从握手衍生出去的安全问题CVE-2016-2183 与 3389 端口加固记录理论讲完实践中的坑一个都躲不掉。说到最近搜索热词里的 CVE-2016-2183以及 3389 端口被扫描器标记为 SSL/TLS 信息泄露的问题这里面其实是一整套“握手算法配置不当导致安全风险”的典型案例。6.1 CVE-2016-2183 到底是个什么漏洞CVE-2016-2183 是 OpenSSL 相关的一个弱密码套件问题本质是3DESTriple DES算法在 64 位分组密码下的安全隐患。3DES 的加密数据量超过一定阈值SWEET32 攻击场景约为 32GB后基于生日攻击原理攻击者可以通过大量采集密文样本获得碰撞块从而逐步恢复明文信息。也就是说只要服务器在密码套件列表里保留了 3DES就存在被长线流量分析破解的理论可能。很多安全扫描器在检测 TLS 配置时只要发现服务器支持TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA或TLS_RSA_WITH_3DES_EDE_CBC_SHA这类套件就会报 CVE-2016-2183。问题不在于算法“立刻被攻破”而在于它已经不符合现代安全基线属于过时密码套件推荐完全禁用。常见的处置方式是在服务端密码套件配置里移除 3DES。Nginx 示例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_ecdh_curve X25519:prime256v1:secp384r1;明确指定椭圆曲线避免使用服务端默认曲线带来的兼容性和强度不确定性。6.2 为什么 3389 端口常被标记为 SSL/TLS 信息泄露3389 是 Windows 远程桌面RDP服务默认端口。很多人以为 RDP 是私有协议跟 SSL/TLS 没关系实际上从 Windows Vista 开始RDP 的传输层安全就可以走 TLS。开启 NLA网络级别身份验证时客户端和服务器之间也会进行 TLS 握手。如果服务器上配置的密码套件里包含 3DES 或弱哈希算法套件安全扫描器如 Nessus、OpenVAS就会把 3389 端口标记为“SSL/TLS 协议信息泄露漏洞CVE-2016-2183”。处理方式是在 Windows 端通过组策略或注册表禁用 3DES。一种常见做法是使用 Windows 的加密套件顺序配置在“本地安全策略”或“组策略编辑器”中进入“计算机配置 - 管理模板 - 网络 - SSL 配置设置”将 SSL 密码套件顺序设置为仅保留 TLS 1.2 和 TLS 1.3 支持的强套件移除所有包含 3DES 的套件。如果服务器不方便做组策略也可以利用 PowerShell 查看当前启用的 TLS 套件定位是否有 3DESGet-TlsCipherSuite | Where-Object { $_.Name -match 3DES }执行后如果返回结果说明当前系统仍启用了 3DES 套件需要将其移除。在 Windows Server 上做完修改后通常需要重启远程桌面服务让新配置生效。6.3 实战中握手失败的排查清单握手失败是日常运维和开发测试中最常见的问题下面按排查顺序给一个清单都是实际踩过坑的总结。第一步确认 TCP 层通不通。用telnet或nc连接服务器端口nc -vz www.example.com 443如果 TCP 都连不上后面全都不用谈。第二步确认 TLS 版本是否兼容。用openssl s_client分别指定 TLS 1.2 和 TLS 1.3 连接openssl s_client -connect www.example.com:443 -tls1_2 openssl s_client -connect www.example.com:443 -tls1_3如果客户端报no protocols available说明客户端和服务器没有共同协议。第三步看证书链是否完整。运行openssl s_client -connect www.example.com:443 -showcerts如果输出里有verify error:num20:unable to get local issuer certificate说明服务器的证书链里缺少中间证书。浏览器通常会自动补全但很多非浏览器客户端如 curl、Java 程序不会补就会报证书错误。第四步检查 SNI 是否正常。如果你用 IP 直连访问但服务器返回了错误的证书大概率是 SNI 没设置。curl 用--resolve指定域名访问curl --resolve www.example.com:443:1.2.3.4 https://www.example.com/这样既能保证请求走指定 IP又能正确携带 SNI。第五步出现sslv3 alert handshake failure时先别慌这是最笼统的错误。通常代表客户端和服务端无法达成一致的密码套件。可以先用--ciphers指定一个明确的套件测试openssl s_client -connect www.example.com:443 -cipher ECDHE-RSA-AES128-GCM-SHA256如果这条能通说明问题出在套件列表的协商上。这时候需要看服务器配置里ssl_ciphers的优先级和客户端支持列表是否有交集。第六步观察证书过期和时钟偏差。客户端验证证书有效期依赖本地时间。如果服务器或客户端系统时钟相差过大即使证书本身正常也会报certificate has expired或certificate is not yet valid。这个坑在虚拟机和容器环境里尤其常见时间同步没做好线上环境就会莫名出现握手失败。6.4 服务端配置建议结合上面所有内容最后给出一个底线配置建议直接可参考协议版本TLS 1.2 TLS 1.3明确禁用 SSLv3、TLS 1.0、TLS 1.1。TLS 1.0 和 1.1 已在多个安全标准中被标记为不推荐继续开启容易在安全扫描里被标记。密码套件只保留 ECDHE 和 DHE 类套件明确移除 3DES、RC4、CBC 模式的弱套件如果兼容性要求高可暂时保留 AES-CBC 但必须配合 TLS 1.2 及以上版本且需关注 SWEET32 之外的 CBC 漏洞能换 GCM 尽量换 GCM。证书保证证书链完整中间证书必须一并部署有条件的开启 OCSP Stapling。会话恢复TLS 1.3 场景下0-RTT 默认关闭除非确实有低延迟需求且业务幂等。定期检查可以用testssl.sh这类工具对线上域名做一次快速扫描看是否有 3DES、RC4、TLS 1.0 等遗留项然后再针对报告逐条加固。我从第一次被面试官追着问“TLS 1.3 为什么没有 RSA 密钥交换”到现在已经用这套方法排查过不少生产环境的握手问题。坦白说握手流程背下来不难难的是每次遇到实际问题时能迅速定位到具体环节——是密码套件不匹配、证书链不完整、SNI 没传、还是协议版本不兼容。把这一整条链路想清楚了不管是准备面试还是做日常运维都不会再觉得 TLS 是一团迷雾。
返回列表