字节面试官:你觉得 HTTPS 能防止重放攻击吗?
我们先来回忆一下 HTTPS 的通信流程,HTTPS 协议 = HTTP 协议 + SSL/TLS 协议,摘取一下网上一些八股文的回答(以 RSA 密钥交换的为例)!
(1)客户端生成一个随机数 client_random,TLS 版本号,发送到服务端
(2)服务端发送自己的随机数 server_random,服务器使用的证书,发送到客户端
(3)客户端利用 CA 公钥对证书进行验证,取出服务器公钥
(4)客户端生成随机数 pre_master_secret,利用服务器公钥进行加密,传送到服务端
(5)服务端利用服务器私钥进行解密取出 pre_master_secret
(6)服务端和客户端此时利用随机数 client_random,server_random,pre_master_secret 算出对称密钥(master_secret),利用对称密钥进行对称加密通信
_画外音:_是不是贼熟悉,有背过网络八股文的,一看就懂!
关键问题就在步骤(6),怎么进行加密的?很多文章都没有说明,甚至有的人以为,拿 client_random+server_random+pre_master_secret 直接拼成一个字符串,然后就是对称加密密钥,客户端和服务端拿这个密钥对数据进行加密通信!!
对此,我只能说:"Too young too simple!离谱啊!!"
那正确的过程是怎么样的呢,我们继续往下看!
协议分析
====
我先给本文提到的英文单词,给上我的中文翻译,以防大家混淆:
client_random 客户端随机数
server_random 服务端随机数
pre_master_secret 预备主密钥
master_secret 主密钥
key_block 密钥块(有的文章把这个东西称为会话密钥)
先大致有个印象,继续往下阅读
现在我们已经有三个参数 client_random,pre_master_secret,server_random,服务端和客户端分别会根据这三个参数,推导出 master_secret,一旦 master_secret 被推导出来,会立刻删除 pre_master_secret。(摘自 rfc2246,section8.1)
当 master_secret 计算出以后,立刻计算 key_block(摘自 rfc2246,section6.3),这个密钥块,有的文章里又说他是会话密钥!计算公式如下,
key_block = PRF(master_secret,
"key expansion",
server_random +
client_random)
如公式所示,PRF 是一个 Hash 算法,如 SHA256 这些,具体用哪一个取决于 TLS 协议的版本!我们得到 key_block 后,可以基于到 key_block 继续推导出 6 个密钥值,分别是
client_write_MAC_key 客户端消息认证码密钥
server_write_MAC_key 服务端消息认证码密钥
client_write_key 客户端对称加密密钥
server_write_key 服务端对称加密密钥
client_write_IV 客户端初始化向量
server_write_IV 服务端初始化向量
整个过程用一张图来说明,注意了这六把密钥是根据 key_block 推导而出,也就是意味着这六把密钥是服务端和客户端共同持有的!
大家一定也发现了,你的密钥前都带有 client 或者 server 前缀,这代表密钥是服务端使用还是客户端使用!例如,客户端用 client_write_key 进行数据加密,发送数据,服务端收到消息后利用 client_write_key 进行解密。而后服务端使用 server_write_key 进行数据加密回复信息,客户端收到消息后用 server_write_key 解密服务端发来的信息!
OK,我们继续往下看!
现在我们已经有了 6 把密钥了,已经需要发送的消息 data,那么加密流程具体怎么样的呢?
TLS 一共有三种加密模
式,流加密模式、分组模式、 AEAD 模式,我以流加密模式来进行说明,如下图所示
我们现在来看上面的第二步,利用 write_mac_key 对数据加密,加上 MAC 验证码,利用 MAC 码来保证完整性。
那么,这个 MAC 验证码的生成公式又是怎么样的呢?
MAC 验证码
======
在流加密模式下,MAC 验证码公式为(摘自 rfc2246,section6.2.3.1)
看到入参中的 seq_num 了么?**这就是数据的序列号,这个序号就是用来防止重放攻击的!**那这个序列号怎么用的呢?
假设,此时服务端和客户端连接成功后
_(1)_客户端会在内存中记录 client_send 和 client_recv,默认值为 0.客户端每发送一条消息,client_send 会加 1,每接收一条服务端发来的消息,client_recv 会加 1。
_(2)_服务端也会在内存中记录 server_send 和 server_recv,作用和客户端的作用一样。服务端每发送一条消息,server_send 会加 1,每接收一条客户端发来的消息,server_recv 会加 1。
_(3)_客户端发送数据时,以当前 client_send 作为 seq_num,计算 mac 值,发送给服务端,然后 client_send 加 1。
评论