https连接建立以及密钥加密详解

一、TCP三次握手
交换https数据之前,客户端和服务端必须先进行TCP三次握手:
- 客户端发送
SYN - 服务端回复
SYN-ACK - 客户端回复
ACK
二、TLS握手(开始传输内容的加密工作)
基于RSA密钥交换算法的TLS握手
-
ClientHello
- 客户端支持的TLS协议版本
- 生成的随机数(用于生成会话密钥的条件之一)
- 客户端支持的密码套件列表:客户端支持的加密算法,如RSA加密算法
-
ServerHello
- 确认TLS版本,浏览器不支持则关闭机密通信
- 服务器生成随机数,后面用于生产会话密钥
- 确认密码套件列表,确认使用什么加密算法
- 服务器的数字证书
Q:数字证书有什么用?为什么要有数字证书?发明数字证书是为了解决什么问题?
A:先说一下什么是对称加密和非对称加密:
- 非对称加密:使用两个密钥,公钥可以任意分发而私钥保密(公钥加密只能用私钥解密,公钥不能解密),可以解决密钥交换问题但速度慢
- 对称加密:只使用一个密钥,运算速度快,但必须保密,无法做到安全的密钥交换
- http采用的是
混合加密:通信建立前第三步客户端回应采用非对称加密,通信过程中全部使用对称加密的会话密钥加密明文数据
接下来聊数字证书:前面收到采用非对称加密,这时我们的内容不想被攻击者窃取,所以使用服务器提供的公钥加密,服务器自己用私钥解密,这样攻击者就解密不了,因为只有私钥可以解密,但是我们有个前提必须保障:服务器的公钥怎么到客户端手里,客户端拿到后又怎么保障这个是来自服务器而不是被人偷偷换掉了
数字证书起的就是一个证明人的作用,服务端的公钥存放在证书中,客户端验证证书的合法性方能拿到公钥。这是数字证书的基本作用,详细后面讲。
-
客户端回应
- 首先通过浏览器或者操作系统中的CA公钥(这个是来验证数字证书的合法性的),确认服务器数字证书的真实性
- 从数字证书中取出服务器的公钥,然后使用它加密报文,向服务端发送信息:
- 一个随机数(被公钥加密)
- 加密通信算法改变通知,随后信息都会使用会话密钥(对称加密)加密
- 客户端握手结束通知,这一项同时把之前所有内容的发生数据做个摘要(发送整个握手内容中所有消息的哈希值),再用会话密钥加密一下,供服务端校验。如果发现异常,服务端会立即发送一个致命警报(Fatal Alert),并终止握手,双方放弃本次连接。不会尝试修复,直接放弃。
Q:摘要算法介绍
A:简单讲就是对传输的内容进行哈希运算计算出哈希值,这个哈希值唯一且不能通过哈希值推导出内容。对方收到后先对内容也计算一个哈希值,进行比较,如果相同说明内容没有被篡改,否则可以判断内容被篡改了。
上面的第一项的随机数就是整个握手阶段的第三个随机数,服务端和客户端都有了这三个随机数,接着就用双方协商的加密算法,各自生成本次通信的会话密钥。
-
服务器的最后回应
服务器收到客户端的第三个随机数之后,通过协商的加密算法,计算本次通信的会话密钥
然后向客户端发送最后的消息:
- 加密通信算法改变通知:表示随后的信息都将用会话密钥加密通信
- 服务器握手结束后,同时把之前所有的内容的发生的数据做个摘要,用来给客户端校验
至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用会话秘钥加密内容。
缺陷 :使用RSA算法最大的问题就是不支持前向保密
因为客户端传递随机数给服务端时使用的是公钥加密,一旦服务端的私钥泄漏了,过去被第三方截获的所有TLS密文都会被破解。为了解决这个问题:后面出现了ECDHE密钥协商算法
基于ECDHE密钥交换算法的TLS握手
ECDHE算法具有前向安全,所以被广泛使用
在这之前,先来推导一下算法的由来,我直接不讲数学,用最通俗方法讲解,有兴趣再去看对应的数学是啥
-
离散对数
一个公式:a^i mod p = b
指数
i称为b的以a为底数的模p的离散对数特点是根据从左往右计算容易,但从右往左计算就很难,尤其是p为很大的质数
text//举个例子 计算3^1000 mod 97 计算机可以快速计算 //反过来 已知3^x mod 97 = 某个数,求x //计算机只能暴力尝试,不断带入x计算Q:为什么P要是很大的质数?
A:安全性取决于可能的取值数量的大小:
- p为质数:可以取到1-p-1的所有值,只能硬试暴力破解;
- p为合数,结果只能取到p-1的一部分,攻击空间大幅减小
-
DH算法
- 现假设小红和小明约定使用 DH 算法来交换密钥,那么基于离散对数,小红和小明需要先确定模数和底数作为算法的参数,这两个参数是公开的,用 P 和 G 来代称。
- 然后小红和小明各自生成一个随机整数作为私钥,双方的私钥要各自严格保管,不能泄漏,小红的私钥用 a 代称,小明的私钥用 b 代称。
- 现在小红和小明双方都有了 P 和 G 以及各自的私钥,于是就可以计算出公钥:
- 小红的公钥记作 A,A = G ^ a ( mod P );
- 小明的公钥记作 B,B = G ^ b ( mod P );
- A 和 B 也是公开的,因为根据离散对数的原理,从真数(A 和 B)反向计算对数 a 和 b 是非常困难的,至少在现有计算机的计算能力是无法破解的
- 双方交换各自 DH 公钥后,小红手上共有 5 个数:P、G、a、A、B,小明手上也同样共有 5 个数:P、G、b、B、A。
- 然后小红执行运算: B ^ a ( mod P ),其结果为 K,因为离散对数的幂运算有交换律,所以小明执行运算: A ^ b ( mod P ),得到的结果也是 K。
这个K就是对称加密密钥,可以作为会话密钥使用
整个过程就公开了4个信息:P,G,A,B,由前面的知识可以知道黑客很难破解,因此DH密钥交换是安全的
-
DHE算法
DH算法分为static DH 算法和 DHE算法,前者指双方中一般是服务端的私钥一直是一样的,所以服务端的公钥是不变的,攻击者可以截获海量密钥协商过程的数据暴力破解服务器的私钥,所以static DH 不具备前向安全性;DHE则是双方每次密钥交换通信的私钥都是随机生成的,临时的。每个通信过程中的私钥都是没有任何关系的,都是独立的,这样就保证了前向安全。
Q:什么是前向安全?
A:前向安全 是一种通过使用"临时密钥交换"技术,确保长期密钥的泄露不会危及过往会话安全的机制。在监控无处不在、数据存储成本极低的时代,前向安全是确保网络通信"即使未来被攻破,过去依然安全"的核心保障。
-
ECDHE算法
DHE算法计算性能不佳,需要做大量乘法,为了提升性能,出现了现在广泛应用于密钥交换的ECDHE算法,本质就是利用ECC椭圆曲线特性,用更少的计算量计算出公钥以及最终的会话密钥。
- 两人事先确定使用哪种椭圆曲线和曲线上的基点G,这两个参数是公开的
- 各自随机生成一个随机数作为私钥d,并与基点G相乘得到公钥Q(Q=dG)
×表示点的数乘 - 双方各自交换,最后小红计算点(x1,y1) = d1Q2,小明计算点(x2,y2) = d2Q1,由于椭圆曲线上是可以满足乘法交换和结合律,所以 d1Q2 = d1d2G = d2d1G = d2Q1 ,因此双方的 x 坐标是一样的,所以它是共享密钥,也就是会话密钥。
-
接下来我们开始捋一下完整的握手过程:
-
ClientHello
客户端使用的TLS版本号
支持的密码套件列表
生成的随机数
-
Server Hello
确认TLS版本号
给出一个随机数
选择一个合适的密码套件:注意!!
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384• 密钥协商算法使用 ECDHE;
• 签名算法使用 RSA:这里的RSA不用于密钥交换而是用于签名,提供身份认证
• 握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM;
• 摘要算法使用 SHA384;
与上面分开发送Certificate消息,发送证书
发送完证书后,发送
Server Key Exchange消息:- 选择椭圆曲线(选好了椭圆曲线相当于椭圆曲线基点G也定好了),公开给客户端
- 生成随机数作为服务端椭圆曲线的私钥,保留到本地
- 根据基点G和私钥计算出服务端的椭圆曲线公钥,公开给客户端
为保证公钥不被第三方篡改,服务端使用RSA签名算法给服务端的公钥做个签名
注意!!:签名 ≠ 加密
这里的签名和公钥是两个独立的数据 ,签名并不是对公钥进行加密,而是通过私钥对其哈希值进行签名,用于保证公钥在传输过程中未被篡改,同时证明该公钥确实来自服务器。
Server Hello Done 打招呼完毕
-
客户端回应
- 证书校验确认服务端身份
- 生成一个随机数作为客户端椭圆曲线私钥,再根据前面服务端的信息生成客户端的椭圆曲线公钥,然后用Client Key Exchange消息发给服务端
至此,双方可以计算出(x,y),其中前面讲x是会话密钥,但在实际应用中,最终的会话密钥是用
客户端随机数+服务端随机数+x三个材料生成的,因为TLS设计者不信任客户端 或服务器伪随机数的可靠性,为了保证真正的完全随机,把三个不可靠的随机数混合起来,随机的程度就非常高了,足够让黑客计算不出最终的会话密钥。算好会话密钥后,客户端会发一个
Change Cipher Spec消息,告诉服务端后续改用对称算法加密通信。接着,客户端会发
Encrypted Handshake Message消息,把之前发送的数据做一个摘要,再用对称密钥加密一下,让服务端做个验证,验证下本次生成的对称密钥是否可以正常使用。 -
服务端回应
最后,服务端也会有一个同样的操作,发Change Cipher Spec和Encrypted Handshake Message消息,如果双方都验证加密和解密没问题,那么握手正式完成。于是,就可以正常收发加密的 HTTP 请求和响应了。
-
三、扩展与补充
RSA与ECDHE握手过程的区别
- RSA 密钥协商算法不支持前向保密,ECDHE 密钥协商算支持前向保密;
- 使用了 RSA 密钥协商算法,TLS 完成四次握手后,才能进行应用数据传输,而对于 ECDHE 算法,客户端可以不用等服务端的最后一次 TLS 握手,就可以提前发出加密的 HTTP 数据,节省了一个消息的往返时间(这个是 RFC 文档规定的,具体原因文档没有说明);
- 使用 ECDHE, 在 TLS 第 2 次握手中,会出现服务器端发出的Server Key Exchange(沟通ECDHE)消息,而 RSA 握手过程没有该消息;
CA证书认证流程
- 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值;
- 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
- 最后将 Certificate Signature 添加在文件证书上,形成数字证书;
客户端校验服务端的数字证书的过程:
-
首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;
-
通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密 Certificate Signature 内容,得到一个 Hash 值 H2 ;
-
最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。
证书信任链
操作系统里一般都会内置一些根证书,我们向CA申请的证书一般不是根证书签发的,而是有中间证书签发的,证书之间有一条链式关系连接,可以类比js的一个事件的全过程。举个例子:
- 客户端收到 baidu.com 的证书后,发现这个证书的签发者不是根证书,就无法根据本地已有的根证书中的公钥去验证 baidu.com 证书是否可信。
- 于是,客户端根据 baidu.com 证书中的签发者,找到该证书的颁发机构是 "GlobalSign Organization Validation CA - SHA256 - G2",然后向 CA 请求该中间证书。
- 请求到证书后发现 "GlobalSign Organization Validation CA - SHA256 - G2" 证书是由 "GlobalSign Root CA" 签发的,由于 "GlobalSign Root CA" 没有再上级签发机构,说明它是根证书,也就是自签证书。
- 应用软件会检查此证书有否已预载于根证书清单上,如果有,则可以利用根证书中的公钥去验证 "GlobalSign Organization Validation CA - SHA256 - G2" 证书,如果发现验证通过,就认为该中间证书是可信的。 "GlobalSign Organization Validation CA - SHA256 - G2" 证书被信任后,可以使用 "GlobalSign Organization Validation CA - SHA256 - G2" 证书中的公钥去验证 baidu.com 证书的可信性,如果验证通过,就可以信任 baidu.com 证书。
Q:为什么需要证书链这么麻烦的流程?Root CA 为什么不直接颁发证书,而是要搞那么多中间层级呢?
- Root CA 私钥是整个信任体系的命脉,频繁用于颁发证书会增加泄露风险
- 中间证书出现问题 只需要处理中间证书,如果是直接办法证书出现问题就要直接换它,所有设备都必须更新信任列表,这是巨大的灾难
TLS1.2与TLS1.3比较
TLS1.3比较于TLS1.2,将密钥交换前置到握手初期,并在服务端问候后立即进入加密通信,精简握手消息并强制使用具备前向安全的密钥交换算法。
| 阶段 | TLS1.2 做什么 | TLS1.3 做什么 | 本质区别 / 优化点 |
|---|---|---|---|
| 客户端问候 | 支持的协议版本- 加密套件列表- 客户端随机数 | 支持的协议版本- 精简的加密套件- 客户端随机数 直接带上椭圆曲线公钥 | TLS1.3 一开始就发送公钥,为后面提前生成密钥做准备 |
| 服务端问候 | 选择协议版本- 选择加密套件- 服务端随机数 | 选择协议版本- 选择加密套件- 服务端随机数 返回自己的椭圆曲线公钥 | TLS1.3 在这里就能完成密钥交换 |
| 密钥交换阶段 | 还未完成,需要后续步骤 | 已完成(双方已能算出共享密钥) | TLS1.3 密钥生成更早 |
| 服务端密钥交换消息 | 有- 椭圆曲线公钥- 用证书私钥做签名 | ------ | TLS1.3 减少消息数量,结构更简洁 |
| 证书传输 | 明文发送证书 | 加密后发送证书 | TLS1.3 保护隐私,避免被窃听分析 |
| 签名验证阶段 | 在"服务端密钥交换"中完成 | 单独通过"证书验证"消息完成 | TLS1.3 职责更清晰(认证与密钥交换分离) |
| 服务端结束标志 | 有(通知客户端服务端发完了) | 没有 | TLS1.3 删除冗余步骤 |
| 客户端密钥交换 | 客户端再发送自己的公钥 | 不需要(前面已经发过) | TLS1.3 减少一次通信 |
| 开始加密通信 | 很晚(在"切换加密状态"之后) | 很早(服务端问候之后就开始) | TLS1.3 握手中后半段全部加密 |
| 切换加密状态 | 必须存在 | 基本废弃(只为兼容) | TLS1.3 流程简化 |
| 完成握手验证 | 最后才验证所有握手数据 | 全程逐步验证(且已加密) | TLS1.3 安全性更强 |
| 握手往返次数 | 需要 2 次往返(2 RTT) | 只需 1 次往返(1 RTT)甚至支持 0 RTT | TLS1.3 明显更快 |
补充 :TLS 1.3 的 0-RTT 握手是其最重要的性能优化之一。它允许客户端在完全建立加密连接之前(即在第一个数据包中)就向服务器发送应用层数据(如 HTTP 请求)。0-RTT 不能 发生在客户端与服务器的第一次连接中。它必须基于之前已经建立过的连接:
- PSK(Pre-Shared Key): 在第一次 TLS 1.3 握手(1-RTT)成功后,服务器会发送一个 NewSessionTicket 消息给客户端。
- 这个 Ticket 包含了一个 PSK(预共享密钥)或者可以推导出 PSK 的信息,以及该 Ticket 的有效期等元数据。
- 客户端会将这个 PSK 存储在本地,用于后续的快速重连。
- 客户端发送(第 1 阶段):
- 客户端在发送 ClientHello 的同时,附带 early_data 扩展。
- 客户端利用之前存储的 PSK 导出"早期密钥"(Early Secret)。
- 关键点: 客户端直接使用这个早期密钥加密 HTTP 请求(例如 GET /index.html),并随 ClientHello 一起发送给服务器。
- 此时,客户端还没收到服务器的任何回复。
- 服务器处理(第 2 阶段):
- 服务器收到 ClientHello,识别出 PSK。
- 服务器使用相同的 PSK 导出"早期密钥",解密并处理随附的加密数据(Early Data)。
- 服务器发送 ServerHello、完成握手,并直接返回应用层响应数据(如 HTML 内容)。
- 结果:
客户端在发出第一个包后,下一个收到的包里可能就包含了业务数据。从应用层角度看,延迟为 0 个往返时延(RTT)。
但是这样存在重放攻击:举个攻击场景:
- 第一步(截获): 客户端在咖啡厅连 WiFi 发送了一个 0-RTT 请求(比如:GET /buy_ticket)。攻击者通过监听网络,把这个二进制数据包原封不动地录下来。
- 第二步(重放): 客户端下线后,攻击者把这个数据包再次发给服务器。
- 第三步(服务器被骗): 服务器收到后,发现 PSK 是对的,解密出来的加密请求也是完整的。服务器并不知道这是攻击者发来的旧包,以为客户端又发起了一次同样的请求。
TLS1.3的风险与性能平衡策略:
- 服务器端抗重放检测: 服务器记录最近收到的 0-RTT 请求的"指纹"。如果短时间内看到重复的指纹,就拒绝它。但这需要大量内存。
- 限制业务场景: 就像之前提到的,只允许**幂等(相同输入都得到相同输出)(GET)**操作。如果攻击者重放了一个"看新闻"的请求,由于看一次和看十次结果一样,重放也就失去了攻击价值。
- 过期时间: 强制 0-RTT 的 Ticket(PSK)在很短时间内失效。
TLS记录协议过程(非握手协议过程)
TLS 记录协议负责保护应用程序数据并验证其完整性和来源,所以对 HTTP 数据加密是使用记录协议;
- 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩。
- 接下来,经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),这是为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。
- 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密钥进行加密。
- 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。
记录协议完成后,最终的报文数据将传递到传输控制协议 (TCP) 层进行传输。
!! 以上是过程是以前的做法,存在一定的安全风险:
-
采用MAC+CBC(一种对称加密的工作模式),会导致Padding Oracle Attack,Lucky13等攻击,具体大家自己了解,但本质就是因为CBC模式需要填充(要求明文长度是块大小的整数倍,块指的是分组密码在进行加密运算时一次性处理的数据单元),解密时先校验填充后校验MAC,攻击者可以利用漏洞不断进行尝试,这里我们以Lucky13进行攻击例子:
- 攻击者发送一个精心修改的密文。
- 服务器处理时:
- 如果填充错误,它会立即返回错误,不计算MAC。
- 如果填充正确,它需要先移除填充,再计算MAC(计算MAC需要遍历整个数据,时间更长)。
- 攻击者通过精确测量响应时间:时间短 = 填充错误 ;时间长 = 填充正确。
- 通过几万次这样的时序试探,攻击者就能像猜密码一样,一字节一字节地解密出整个会话的Cookie、密码等敏感信息。
-
解决方法:采用更先进的AEAD机制
-
一次性完成认证和加密,减少实现错误
-
CBC模式需要填充,AEAD不需要填充,根本上移除了填充相关的攻击面
-
AEAD 是 先验证,后解密 。CBC+MAC 是 先解密,后验证。先验证意味着攻击者连触发解密的机会都没有,也就无法通过操纵解密过程中的中间状态来获取信息。
-
CBC+MAC 模式 :它的验证路径是 明文依赖型。
-
先解密 密文 -> 得到明文。
-
检查明文中的填充是否合法 -> 这个检查完全依赖于解密后的明文内容。
填充字符和明文字符之间确实存在这种可被利用的数学关联,这正是 Padding Oracle Attack 能够成功破解明文的根本原因 -
如果填充合法,再对明文计算 MAC。
-
攻击者正是利用了"填充是否合法"这个依赖于明文 且容易出错的步骤,通过观察服务器反应(错误信息或时间)来逐步猜解明文。解密过程与明文内容产生了条件分支(例如,if 填充合法 then 做某事)。
-
-
AEAD 模式 :它的验证路径是 密文+标签依赖型。
- 同时使用密文、附加数据(AAD)、密钥和随机数(Nonce)来计算一个预期的认证标签。
- 将这个预期标签与通信中附带的实际标签进行比较。
- 只有在标签验证完全通过之后,才会进行解密操作。
- 攻击者无法操纵"填充"来创造不同的状态,因为根本没有填充。验证过程只有一个分支:标签对还是错?
-
-