mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
743 lines
30 KiB
HTML
743 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>26 信任始于握手:TLS1.2连接过程解析.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/00 开篇词|To Be a HTTP Hero.md.html">00 开篇词|To Be a HTTP Hero.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/01 时势与英雄:HTTP的前世今生.md.html">01 时势与英雄:HTTP的前世今生.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/02 HTTP是什么?HTTP又不是什么?.md.html">02 HTTP是什么?HTTP又不是什么?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/03 HTTP世界全览(上):与HTTP相关的各种概念.md.html">03 HTTP世界全览(上):与HTTP相关的各种概念.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/04 HTTP世界全览(下):与HTTP相关的各种协议.md.html">04 HTTP世界全览(下):与HTTP相关的各种协议.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/05 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?.md.html">05 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/06 域名里有哪些门道?.md.html">06 域名里有哪些门道?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/07 自己动手,搭建HTTP实验环境.md.html">07 自己动手,搭建HTTP实验环境.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/08 键入网址再按下回车,后面究竟发生了什么?.md.html">08 键入网址再按下回车,后面究竟发生了什么?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/09 HTTP报文是什么样子的?.md.html">09 HTTP报文是什么样子的?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/10 应该如何理解请求方法?.md.html">10 应该如何理解请求方法?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/11 你能写出正确的网址吗?.md.html">11 你能写出正确的网址吗?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/12 响应状态码该怎么用?.md.html">12 响应状态码该怎么用?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/13 HTTP有哪些特点?.md.html">13 HTTP有哪些特点?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/14 HTTP有哪些优点?又有哪些缺点?.md.html">14 HTTP有哪些优点?又有哪些缺点?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/15 海纳百川:HTTP的实体数据.md.html">15 海纳百川:HTTP的实体数据.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/16 把大象装进冰箱:HTTP传输大文件的方法.md.html">16 把大象装进冰箱:HTTP传输大文件的方法.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/17 排队也要讲效率:HTTP的连接管理.md.html">17 排队也要讲效率:HTTP的连接管理.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/18 四通八达:HTTP的重定向和跳转.md.html">18 四通八达:HTTP的重定向和跳转.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/19 让我知道你是谁:HTTP的Cookie机制.md.html">19 让我知道你是谁:HTTP的Cookie机制.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/20 生鲜速递:HTTP的缓存控制.md.html">20 生鲜速递:HTTP的缓存控制.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/21 良心中间商:HTTP的代理服务.md.html">21 良心中间商:HTTP的代理服务.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/22 冷链周转:HTTP的缓存代理.md.html">22 冷链周转:HTTP的缓存代理.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/23 HTTPS是什么?SSLTLS又是什么?.md.html">23 HTTPS是什么?SSLTLS又是什么?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/24 固若金汤的根本(上):对称加密与非对称加密.md.html">24 固若金汤的根本(上):对称加密与非对称加密.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/25 固若金汤的根本(下):数字签名与证书.md.html">25 固若金汤的根本(下):数字签名与证书.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a class="current-tab" href="/专栏/透视HTTP协议/26 信任始于握手:TLS1.2连接过程解析.md.html">26 信任始于握手:TLS1.2连接过程解析.md.html</a>
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/27 更好更快的握手:TLS1.3特性解析.md.html">27 更好更快的握手:TLS1.3特性解析.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/28 连接太慢该怎么办:HTTPS的优化.md.html">28 连接太慢该怎么办:HTTPS的优化.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/29 我应该迁移到HTTPS吗?.md.html">29 我应该迁移到HTTPS吗?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/30 时代之风(上):HTTP2特性概览.md.html">30 时代之风(上):HTTP2特性概览.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/31 时代之风(下):HTTP2内核剖析.md.html">31 时代之风(下):HTTP2内核剖析.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/32 未来之路:HTTP3展望.md.html">32 未来之路:HTTP3展望.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/33 我应该迁移到HTTP2吗?.md.html">33 我应该迁移到HTTP2吗?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/34 Nginx:高性能的Web服务器.md.html">34 Nginx:高性能的Web服务器.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/35 OpenResty:更灵活的Web服务器.md.html">35 OpenResty:更灵活的Web服务器.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/36 WAF:保护我们的网络服务.md.html">36 WAF:保护我们的网络服务.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/37 CDN:加速我们的网络服务.md.html">37 CDN:加速我们的网络服务.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/38 WebSocket:沙盒里的TCP.md.html">38 WebSocket:沙盒里的TCP.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/39 HTTP性能优化面面观(上).md.html">39 HTTP性能优化面面观(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/40 HTTP性能优化面面观(下).md.html">40 HTTP性能优化面面观(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/透视HTTP协议/结束语 做兴趣使然的Hero.md.html">结束语 做兴趣使然的Hero.md.html</a>
|
||
</li>
|
||
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>26 信任始于握手:TLS1.2连接过程解析</h1>
|
||
|
||
<p>经过前几讲的介绍,你应该已经熟悉了对称加密与非对称加密、数字签名与证书等密码学知识。</p>
|
||
|
||
<p>有了这些知识“打底”,现在我们就可以正式开始研究 HTTPS 和 TLS 协议了。</p>
|
||
|
||
<h2>HTTPS 建立连接</h2>
|
||
|
||
<p>当你在浏览器地址栏里键入“<strong>https</strong>”开头的 URI,再按下回车,会发生什么呢?</p>
|
||
|
||
<p>回忆一下[第 8 讲]的内容,你应该知道,浏览器首先要从 URI 里提取出协议名和域名。因为协议名是“https”,所以浏览器就知道了端口号是默认的 443,它再用 DNS 解析域名,得到目标的 IP 地址,然后就可以使用三次握手与网站建立 TCP 连接了。</p>
|
||
|
||
<p>在 HTTP 协议里,建立连接后,浏览器会立即发送请求报文。但现在是 HTTPS 协议,它需要再用另外一个“握手”过程,在 TCP 上建立安全连接,之后才是收发 HTTP 报文。</p>
|
||
|
||
<p>这个“握手”过程与 TCP 有些类似,是 HTTPS 和 TLS 协议里最重要、最核心的部分,懂了它,你就可以自豪地说自己“掌握了 HTTPS”。</p>
|
||
|
||
<h2>TLS 协议的组成</h2>
|
||
|
||
<p>在讲 TLS 握手之前,我先简单介绍一下 TLS 协议的组成。</p>
|
||
|
||
<p>TLS 包含几个子协议,你也可以理解为它是由几个不同职责的模块组成,比较常用的有记录协议、警报协议、握手协议、变更密码规范协议等。</p>
|
||
|
||
<p><strong>记录协议</strong>(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。</p>
|
||
|
||
<p><strong>警报协议</strong>(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。</p>
|
||
|
||
<p><strong>握手协议</strong>(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。</p>
|
||
|
||
<p>最后一个是<strong>变更密码规范协议</strong>(Change Cipher Spec Protocol),它非常简单,就是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。</p>
|
||
|
||
<p>下面的这张图简要地描述了 TLS 的握手过程,其中每一个“框”都是一个记录,多个记录组合成一个 TCP 包发送。所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。</p>
|
||
|
||
<p><img src="assets/69493b53f1b1d540acf886ebf021a26c.png" alt="img" /></p>
|
||
|
||
<h2>抓包的准备工作</h2>
|
||
|
||
<p>这次我们在实验环境里测试 TLS 握手的 URI 是“/26-1”,看了上面的图你就可以知道,TLS 握手的前几个消息都是明文的,能够在 Wireshark 里直接看。但只要出现了“Change Cipher Spec”,后面的数据就都是密文了,看到的也就会是乱码,不知道究竟是什么东西。</p>
|
||
|
||
<p>为了更好地分析 TLS 握手过程,你可以再对系统和 Wireshark 做一下设置,让浏览器导出握手过程中的秘密信息,这样 Wireshark 就可以把密文解密,还原出明文。</p>
|
||
|
||
<p>首先,你需要在 Windows 的设置里新增一个系统变量“<strong>SSLKEYLOGFILE</strong>”,设置浏览器日志文件的路径,比如“D:\http_study\www\temp\sslkey.log”(具体的设置过程就不详细说了,可以在设置里搜索“系统变量”)。</p>
|
||
|
||
<p><img src="assets/70b36338611d5a249a7d2fc448f06d42.png" alt="img" /></p>
|
||
|
||
<p>然后在 Wireshark 里设置“Protocols-TLS”(较早版本的 Wireshark 里是“SSL”),在“(Pre)-Master-Secret log filename”里填上刚才的日志文件。</p>
|
||
|
||
<p><img src="assets/0274e31e74e92b61892ec11cc3cd58e7.png" alt="img" /></p>
|
||
|
||
<p>设置好之后,过滤器选择“<strong>tcp port 443</strong>”,就可以抓到实验环境里的所有 HTTPS 数据了。</p>
|
||
|
||
<p>如果你觉得麻烦也没关系,GitHub 上有抓好的包和相应的日志,用 Wireshark 直接打开就行。</p>
|
||
|
||
<h2>ECDHE 握手过程</h2>
|
||
|
||
<p>刚才你看到的是握手过程的简要图,我又画了一个详细图,对应 Wireshark 的抓包,下面我就用这个图来仔细剖析 TLS 的握手过程。</p>
|
||
|
||
<p><img src="assets/9caba6d4b527052bbe7168ed4013011e.png" alt="img" /></p>
|
||
|
||
<p>在 TCP 建立连接之后,浏览器会首先发一个“<strong>Client Hello</strong>”消息,也就是跟服务器“打招呼”。里面有客户端的版本号、支持的密码套件,还有一个<strong>随机数(Client Random)</strong>,用于后续生成会话密钥。</p>
|
||
|
||
<pre><code>Handshake Protocol: Client Hello
|
||
|
||
Version: TLS 1.2 (0x0303)
|
||
|
||
Random: 1cbf803321fd2623408dfe…
|
||
|
||
Cipher Suites (17 suites)
|
||
|
||
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
|
||
|
||
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
|
||
|
||
</code></pre>
|
||
|
||
<p>这个的意思就是:“我这边有这些这些信息,你看看哪些是能用的,关键的随机数可得留着。”</p>
|
||
|
||
<p>作为“礼尚往来”,服务器收到“Client Hello”后,会返回一个“Server Hello”消息。把版本号对一下,也给出一个<strong>随机数(Server Random)</strong>,然后从客户端的列表里选一个作为本次通信使用的密码套件,在这里它选择了“TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384”。</p>
|
||
|
||
<pre><code>Handshake Protocol: Server Hello
|
||
|
||
Version: TLS 1.2 (0x0303)
|
||
|
||
Random: 0e6320f21bae50842e96…
|
||
|
||
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
|
||
|
||
</code></pre>
|
||
|
||
<p>这个的意思就是:“版本号对上了,可以加密,你的密码套件挺多,我选一个最合适的吧,用椭圆曲线加 RSA、AES、SHA384。我也给你一个随机数,你也得留着。”</p>
|
||
|
||
<p>然后,服务器为了证明自己的身份,就把证书也发给了客户端(Server Certificate)。</p>
|
||
|
||
<p>接下来是一个关键的操作,因为服务器选择了 ECDHE 算法,所以它会在证书后发送“<strong>Server Key Exchange</strong>”消息,里面是<strong>椭圆曲线的公钥(Server Params)</strong>,用来实现密钥交换算法,再加上自己的私钥签名认证。</p>
|
||
|
||
<pre><code>Handshake Protocol: Server Key Exchange
|
||
|
||
EC Diffie-Hellman Server Params
|
||
|
||
Curve Type: named_curve (0x03)
|
||
|
||
Named Curve: x25519 (0x001d)
|
||
|
||
Pubkey: 3b39deaf00217894e...
|
||
|
||
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
|
||
|
||
Signature: 37141adac38ea4...
|
||
|
||
</code></pre>
|
||
|
||
<p>这相当于说:“刚才我选的密码套件有点复杂,所以再给你个算法的参数,和刚才的随机数一样有用,别丢了。为了防止别人冒充,我又盖了个章。”</p>
|
||
|
||
<p>之后是“<strong>Server Hello Done</strong>”消息,服务器说:“我的信息就是这些,打招呼完毕。”</p>
|
||
|
||
<p>这样第一个消息往返就结束了(两个 TCP 包),结果是客户端和服务器通过明文共享了三个信息:<strong>Client Random、Server Random 和 Server Params</strong>。</p>
|
||
|
||
<p>客户端这时也拿到了服务器的证书,那这个证书是不是真实有效的呢?</p>
|
||
|
||
<p>这就要用到第 25 讲里的知识了,开始走证书链逐级验证,确认证书的真实性,再用证书公钥验证签名,就确认了服务器的身份:“刚才跟我打招呼的不是骗子,可以接着往下走。”</p>
|
||
|
||
<p>然后,客户端按照密码套件的要求,也生成一个<strong>椭圆曲线的公钥(Client Params)</strong>,用“<strong>Client Key Exchange</strong>”消息发给服务器。</p>
|
||
|
||
<pre><code>Handshake Protocol: Client Key Exchange
|
||
|
||
EC Diffie-Hellman Client Params
|
||
|
||
Pubkey: 8c674d0e08dc27b5eaa…
|
||
|
||
</code></pre>
|
||
|
||
<p>现在客户端和服务器手里都拿到了密钥交换算法的两个参数(Client Params、Server Params),就用 ECDHE 算法一阵算,算出了一个新的东西,叫“<strong>Pre-Master</strong>”,其实也是一个随机数。</p>
|
||
|
||
<p>至于具体的计算原理和过程,因为太复杂就不细说了,但算法可以保证即使黑客截获了之前的参数,也是绝对算不出这个随机数的。</p>
|
||
|
||
<p>现在客户端和服务器手里有了三个随机数:<strong>Client Random、Server Random 和 Pre-Master</strong>。用这三个作为原始材料,就可以生成用于加密会 话的主密钥,叫“<strong>Master Secret</strong>”。而黑客因为拿不到“Pre-Master”,所以也就得不到主密钥。</p>
|
||
|
||
<p>为什么非得这么麻烦,非要三个随机数呢?</p>
|
||
|
||
<p>这就必须说 TLS 的设计者考虑得非常周到了,他们不信任客户端或服务器伪随机数的可靠性,为了保证真正的“完全随机”“不可预测”,把三个不可靠的随机数混合起来,那么“随机”的程度就非常高了,足够让黑客难以猜测。</p>
|
||
|
||
<p>你一定很想知道“Master Secret”究竟是怎么算出来的吧,贴一下 RFC 里的公式:</p>
|
||
|
||
<pre><code>master_secret = PRF(pre_master_secret, "master secret",
|
||
|
||
ClientHello.random + ServerHello.random)
|
||
|
||
</code></pre>
|
||
|
||
<p>这里的“PRF”就是伪随机数函数,它基于密码套件里的最后一个参数,比如这次的 SHA384,通过摘要算法来再一次强化“Master Secret”的随机性。</p>
|
||
|
||
<p>主密钥有 48 字节,但它也不是最终用于通信的会话密钥,还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。</p>
|
||
|
||
<p>有了主密钥和派生的会话密钥,握手就快结束了。客户端发一个“<strong>Change Cipher Spec</strong>”,然后再发一个“<strong>Finished</strong>”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。</p>
|
||
|
||
<p>意思就是告诉服务器:“后面都改用对称算法加密通信了啊,用的就是打招呼时说的 AES,加密对不对还得你测一下。”</p>
|
||
|
||
<p>服务器也是同样的操作,发“<strong>Change Cipher Spec</strong>”和“<strong>Finished</strong>”消息,双方都验证加密解密 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。</p>
|
||
|
||
<h2>RSA 握手过程</h2>
|
||
|
||
<p>整个握手过程可真是够复杂的,但你可能会问了,好像这个过程和其他地方看到的不一样呢?</p>
|
||
|
||
<p>刚才说的其实是如今主流的 TLS 握手过程,这与传统的握手有两点不同。</p>
|
||
|
||
<p>第一个,使用 ECDHE 实现密钥交换,而不是 RSA,所以会在服务器端发出“Server Key Exchange”消息。</p>
|
||
|
||
<p>第二个,因为使用了 ECDHE,客户端可以不用等到服务器发回“Finished”确认握手完毕,立即就发出 HTTP 报文,省去了一个消息往返的时间浪费。这个叫“<strong>TLS False Start</strong>”,意思就是“抢跑”,和“TCP Fast Open”有点像,都是不等连接完全建立就提前发应用数据,提高传输的效率。</p>
|
||
|
||
<p>实验环境在 440 端口(<a href="https://www.chrono.com:440/26-1">https://www.chrono.com:440/26-1</a>)实现了传统的 RSA 密钥交换,没有“False Start”,你可以课后自己抓包看一下,这里我也画了个图。</p>
|
||
|
||
<p><img src="assets/cb9a89055eadb452b7835ba8db7c3ad2.png" alt="img" /></p>
|
||
|
||
<p>大体的流程没有变,只是“Pre-Master”不再需要用算法生成,而是客户端直接生成随机数,然后用服务器的公钥加密,通过“<strong>Client Key Exchange</strong>”消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。</p>
|
||
|
||
<h2>双向认证</h2>
|
||
|
||
<p>到这里 TLS 握手就基本讲完了。</p>
|
||
|
||
<p>不过上面说的是“<strong>单向认证</strong>”握手过程,只认证了服务器的身份,而没有认证客户端的身份。这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。</p>
|
||
|
||
<p>但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现“<strong>双向认证</strong>”,这样会更加安全。</p>
|
||
|
||
<p>双向认证的流程也没有太多变化,只是在“<strong>Server Hello Done</strong>”之后,“<strong>Client Key Exchange</strong>”之前,客户端要发送“<strong>Client Certificate</strong>”消息,服务器收到后也把证书链走一遍,验证客户端的身份。</p>
|
||
|
||
<h2>小结</h2>
|
||
|
||
<p>今天我们学习了 HTTPS/TLS 的握手,内容比较多、比较难,不过记住下面四点就可以。</p>
|
||
|
||
<ol>
|
||
|
||
<li>HTTPS 协议会先与服务器执行 TCP 握手,然后执行 TLS 握手,才能建立安全连接;</li>
|
||
|
||
<li>握手的目标是安全地交换对称密钥,需要三个随机数,第三个随机数“Pre-Master”必须加密传输,绝对不能让黑客破解;</li>
|
||
|
||
<li>“Hello”消息交换随机数,“Key Exchange”消息交换“Pre-Master”;</li>
|
||
|
||
<li>“Change Cipher Spec”之前传输的都是明文,之后都是对称密钥加密的密文。</li>
|
||
|
||
</ol>
|
||
|
||
<h2>课下作业</h2>
|
||
|
||
<ol>
|
||
|
||
<li>密码套件里的那些算法分别在握手过程中起了什么作用?</li>
|
||
|
||
<li>你能完整地描述一下 RSA 的握手过程吗?</li>
|
||
|
||
<li>你能画出双向认证的流程图吗?</li>
|
||
|
||
</ol>
|
||
|
||
<p>欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。</p>
|
||
|
||
<p><img src="assets/93d002084d9bf8283bab3e34e3f4bf14.png" alt="unpreview" /></p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/透视HTTP协议/25 固若金汤的根本(下):数字签名与证书.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/透视HTTP协议/27 更好更快的握手:TLS1.3特性解析.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997d198a6e3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
</script>
|
||
</html>
|
||
|