阿main面试篇之HTTPS终结篇

2020-04-21 16:02:59 浏览数 (1)

大家好,我是与秃顶法师老赵旗鼓相当的谢顶道人 --- 老李。

本文关键词:HTTPS、DES、RSA、AES、GCM、DH、ECDH、DHE、ECDHE

本文字数:6600字

预计阅读时间:40-60分钟

众所周知,main顺利过了一面正在等待二面中,很显然:没有人比main更懂HTTPS了。上一篇文章粗暴地理清楚了HTTPS的大概流程,提出了几个关键技术词语,今天我们得深入一下HTTPS的具体细节了。上一篇链接:

阿main面试记之HTTPS篇:没有人比我更懂HTTPS了

我假定你们已经整明白了上一篇文章中出现的几个基础名词:

  • 非对称结加解密
  • 对称加解密
  • 密钥协商
  • 数字签名
  • 单向散列
  • 数字证书
  • CA机构

今儿我就不废话了啊,直接开始正题了,让我们来完整地体验一把HTTPS流程,我们找了四个人来临时客串一把这个流程。

服务器老李

客户端旅长

CA机构老赵

中间人山本


第一步:服务器之老李的准备工作

老李得先成自己的一对非对称公私钥,这个实际上非常简单,说出来你可能不信,其实就是openssl就能搞干这些破事儿,非常简单:

代码语言:javascript复制
// 生成服务器私钥
openssl genrsa -out server_private.key 1024
// 根据私钥生成公钥
openssl rsa -in server_private.key -pubout -out server_public.pem

这里就是利用RSA非对称算法生成了一对公私钥,看到这里知道为啥说公私钥必须是成双成对了吧?记住了,私钥藏好了藏到自己裤裆里,公钥爱咋咋地。

这里老李作为服务器提供方,需要找CA机构老赵为自己颁发自己的数字证书。这里的流程是:CA机构一般是接受申请者的CSR文件。CSR是Certificate Signing Request的英文缩写,即证书请求文件。那么老李如何搞到自己的CSR文件呢?非常简单,依然使用openssl就可以:

代码语言:javascript复制
// 利用自己的私钥生成自己的csr文件!
// 注意别搞错了,不是利用自己的公钥生成csr文件,而是私钥
openssl req -new -key server_private.key -out server.csr

这个CSR文件中,实际上就包含了公钥内容,这一点十分十分重要请记住,你们可以打开server_public.pem公钥文件和server.csr文件肉眼对比一下。

由于这一步都是在本地服务器上老李自己的地盘上完成的,所以这一步不存在任何风险因素。


第二步:CA机构之老赵的准备工作

老赵就是个躺着赚钱恰饭的CA机构,在经过了晋西北人民的同意后依法成立,正式挂牌运营前,老赵得率先生成自己的私钥和证书文件:

代码语言:javascript复制
// 生成CA机构的私钥
openssl genrsa -out ca_private.key 1024
// 再生成CA机构自己的公钥证书
openssl x509 -req -in ca.csr -signkey ca_private.key -out ca.crt

这一步由于是老赵在自家地盘搞定的,所以也不存在潜在风险因素。


第三步:CA机构老赵收到服务器老李的CSR文件

老李有各种办法将CSR文件提交给CA机构老赵,一般来说都是通过网络来完成。而CA机构老赵收到老李的CSR文件后,需要确认这个CSR文件到底是不是老李的。这里途径很多,而且每家CA机构都有自己CPS认证行为标准,比如说可以通过DNS域名校验,甚至高安全级别的需要申请方提供企业的工商证书,甚至当面验证 ID身份,总之是要确认这个CSR文件是老李的。你们申请过Letsencrypt的HTTPS证书吗?还记得Letsencrypt用的啥认证不?

在确认完完毕后,老赵就用下面命令给老李颁发一个数字证书,当然了亲兄弟还得明算帐,颁发数字证书可是得要恰饭收费的(Letsencrypt是公益王者):

代码语言:javascript复制
// 老赵为老李的公钥颁发数字证书
openssl x509 -req -CA ca.crt -CAkey ca_private.key -CAcreateserial -in server.csr -out server.crt

这个server.crt就是数字证书咯,下面只需要把这个server.crt扔给服务器老李就可以了。

注意这个流程过程中,我们注意到老赵和老李中间互相彼此飞了一次数据,就已经会产生潜在风险因素了,我们来逐个分析一下。

首先当老李把自己的CSR文件传给老赵的时候,如果中间人山本出现了,山本顺利地潜入了赵家裕,劫持了秀芹...哦不,劫持了老赵和老李之间的网络会话,此时山本可以做两件事:

  • 一、山本冒充CA机构给老李颁发数字证书。但并没有什么乱用,因为CA机构的公钥都是提前内置在各个操作系统的,像山本这种山寨CA机构的公钥都压根到不了操作系统那里,也就是说到不了客户端那里,客户端在访问服务器时候,根本无法找到相应的CA公钥对服务器返回的山寨数字证书进行逆向解密,GG
  • 二、山本随意篡改一下老李的CSR。这也并没有什么卵用,因为老李生成的CSR文件时候(看下那个命令)本质上就是利用自己的RSA私钥对内容进行数字签名,而老赵再利用命令为老李的CSR文件生成数字签名时候,有一个潜在流程就是从CSR文件中提取出老李的公钥,然后利用RSA非对称算法进行逆向解密验签,一旦CSR文件任何一处被篡改,此处就会直接失败!这里利用的就是RSA非对称算法中的「私钥加密,公钥解密」来验证数字签名。不信你们可以动手修改一下CSR文件,看看还能不能生成数字证书,不出意外会返回如下:

所以此处我们只需要无条件信任RSA算法的可靠性即可。

除此之外,最重要的是生成这个数字证书的过程:首先对老李的公钥进行「单向散列」(就是哈希算法)运算生成一个摘要值(就是哈希数值),然后再使用RSA非对称加密对这个摘要值进行数字签名,也就是使用CA私钥对这个摘要值直接加密!(PS:就是说使用私钥加密就是数字签名)。


第四步:服务器之老李拿到了数字证书

这里就是我们俗称的我们申请到了HTTPS证书,一般说来下面就是配置一下HTTP服务器让TA支持HTTPS服务即可,这一步也是老李在自家晋西北的底盘上搞的,山本讲道理是整不了什么幺蛾子的。比如说Nginx,一般这样配置就可以了:

代码语言:javascript复制
listen 443;
listen [::]:443;
ssl on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
# https数字证书
ssl_certificate /home/ubuntu/server.crt;
# 服务器私钥
ssl_certificate_key /home/ubuntu/server_private.key;

重启一下Nginx服务器不出意外现在443端口就已经安排上了,下面只需要让作为客户端的旅长联系一下老李就行了。众所周知,老李的战场抗命是出了名的,不仅旅长有时候找不到他,连彭老总都联系不上。先联系一下试试,如果不出意外的话,你们那里应该也是这样的:

很显然在我们的文章里,老赵的CA机构是个经过了晋西北广大群众承认的CA颁发机构,然而现实里老赵的CA机构就是一坨屎,一个活脱脱的山寨CA版本机构...所以这个错误在预料之中,但是文章还是得写下去不是?那么如何让才能假装我们这个HTTPS数字证书是个「正规」证书呢?

话说到这里,你们有没有联想到前几年12306,那会儿如果要使用12306就必须安装信任12306的根证书,因为那会儿12306其实就是自签的数字证书。当然了这两年换成CA机构颁发的数字证书了。

所以这里要做的就是将数字证书添加到你操作系统的信任证书列表就行了。这一步操作后,不出意外刷新下浏览器就OK了。

思考:各位泥腿子们经常用著名HTTP抓包软件青花瓷吧?到这里为止,你是否已经知道为什么要抓HTTPS的包必须要安装青花瓷提供的根证书了吗?


第五步:客户端之旅长呼叫老李

旅长打开浏览器输入老李的网址后,老李第一时间会返回给旅长数字证书。而旅长也会从自己这里搜索到早已内置到系统中的CA机构老赵的公钥(此时我们就假装老赵的证书是一个正规CA机构颁发的)。这个过程,值得我们详细说说。

首先可以确认动作是由旅长率先发起的,假如说TCP三次握手已经完成了,旅长和老李的通信信道已经建立完毕,开始发送数据:

旅长会准备好如下数据:客户端所支持的TLS/SSL版本,一个Random随机数(要用于「密钥协商」),客户端支持的加密算法Cipher Suite(17表示客户端支持17种),Session ID(这个是为了尽量复用好不容易才能建立起来的HTTPS链接)。一般专业地说这个包叫做Client Hello包,我用wireshark给大家抓包感受一下


第六步:老李反馈旅长(此步高能)

老李收到旅长呼叫后:会确认TLS/SSL版本,保存住随机数(要用于「密钥协商」)Random,确认后面即将使用的加密算法Cipher Suite,最后最重要的就是返回数字证书Certificate。一般专业地说,服务器会返回大概三个包,一个叫做Server Hello包、一个叫Certificate包、一个叫做Sever Hello Done && Server Key Exchange包,依然wireshare截图给大家感受下

这里特殊说明下Server Hello里返回地Cipher Suite字段,它的值是:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,告诉我你们崩溃了吗?这个就表示服务器选用这个进行后面的加密了,TA表示「使用TLS协议,使用ECDHE-RSA非对称协商及密钥交换,使用AES_256_GCM进行HTTPS协议后面的对称加解密构成,SHA384单向散列方法进行摘要」,其中AES_256_GCM种AES表示对称加解密的具体算法,256表示对称加解密密钥长度,GCM是对称加解密的分组算法!

这个包就是服务端老李返回给旅长客户端的数字证书了。

这里值得注意的是TLS将Server Hello Done和Server Key Exchange合成了一个包,我们只需要注意Server Key Exchange中,老李的服务器端已经准备使用ECDHE与旅长的客户端进行密钥协商交换了(还记得老李说过的DH密钥协商不?ECDH就是椭圆曲线版本的DH,我后面抽时间说下,而ECDHE最后的E表示公钥临时生成,我觉得你们可能已经崩溃了)。而Pubkey就是ECDHE密钥协商的必要参数之一!这个Pubkey是下发给客户端的。(此处需要补充说明:服务器选择了ECDHE密钥协商情况下,才会出现Server Key Exchange包,在本次抓包案例中服务器选择的方法是ECDHE。常用的密钥协商方法有DH、ECDH、DHE、ECDHE、RSA等)

思考:上一篇中我们说单纯使用RSA的问题就是,偷窥的山本劫持了旅长和老李的会话,在老李面前冒充旅长,在旅长面前冒充老李,主要矛盾就是山本给老李返回了一个「老李自认为是来自于旅长的公钥」,自此之后,所有一切其他安全行为都形同虚设。那么我们再有了CA机构的数字证书的力量加持下,如果此时山本又介入到了老李和旅长中间,山本就必须要做一个山寨的数字证书返回给老李,让老李误认为这个数字证书是旅长的,考虑下,山本的愿望还可以实现吗?


第七步:旅长收到反馈

旅长第一时间收到数字证书,他要做很重要的是一件事那就是验证数字证书是否是正规的数字证书。首先验证证书颁发机构是否为上级的CA机构,然后检验证书有效期,校验证书中域名和当前访问域名是否匹配。

当旅长发现这些客观条件都满足后,他就要对数字证书进行数字验签 散列对比了。首先使用CA机构预发在操作系统中的CA公钥对数字证书进行数字验签,本质上就是RSA公钥解密,得到的是一个散列值。由于数字证书中已经告知了该散列值所用的「单向散列」算法,所以客户端也利用相同的「单向散列」算法对老李的公钥单向散列一下得到一个散列值,然后将该散列值与数字验签后的散列值进行对比即可,如果一模一样,那就说明数字证书没问题,也就保证了得到的「服务器老李的公钥」没问题。

其实到这会儿浏览器上那个小?标志应该就是绿色的了。

总之,反正搞定数字证书确实没问题后,旅长的客户端向老李的服务端发起Client Key Exchange包,这一步里重要参数依然是Pubkey,这个Pubkey是传给服务端的。(此处与Server Key Exchange遥相呼应,我要说的是由于服务器可能选择的密钥协商方法会有不同种,实际上Client Key Exchange种的参数总是会发生变化,因为不同的密钥协商方法需要的参数不一样,在本次抓包案例中服务器选择的方法是ECDHE。常用的密钥协商方法有DH、ECDH、DHE、ECDHE、RSA等)

当客户端这个Client Key Exchange发送过去后,在当前ECDHE密钥协商的前提之下,此时HTTPS流程已经到了一个关键的节骨眼上:就是双方开始利用密钥协商算法各自在算出用于对称加密的密钥!(还记得上一篇里老李提到的案例吗?A和B先沟通下都选择好一个相同的数字100(可以公开),然后A自己再选定一个数字比如3(藏到裤裆里),B也自己心里默默选好一个数字比如9(藏到裤裆里),A将3乘以100得到300(可以公开),B将9乘以100得到900(可以公开),然后AB之间互相交换300和900,截止到目前为止A拥有100,3,900三个数字,而B拥有100,9,300三个数字,看好了!A将3乘以900得到2700,B将9乘以300得到2700,2700可以作为一个密钥进行加解密通信了,而且整个过程中2700没有在网络上进行传播)

那么ECDHE究竟是怎样协商的呢?一定不可能像上面小括弧案例里A和B这么粗暴而又邋遢(兄弟们啊,顶住啊!马上就要结尾了!):

  • 使用世界公认的椭圆曲线的一个基点Q(x,y),公开的大家都知道
  • 客户端Client Hello时候有个Random,我们称之为「R客户端」,然后令「R客户端pubkey」=「R客户端」*Q(x,y),这个「R客户端pubkey」其实就是Client Key Exchange里发送给服务器的pubkey
  • 服务端Server Hello时候有个Random,我们称之为「R服务器」,然后令「R服务端pubkey」=「R服务器」*Q(x,y),这个「R服务端pubkey」其实就是Server Key Exchange里发送给客户端的pubkey
  • 现在客户端拥有「R服务端pubkey」与「R客户端」,计算S=「R服务端pubkey」*「R客户端」
  • 现在服务端拥有「R客户端pubkey」与「R服务器」,计算S=「R客户端pubkey」*「R服务器」
  • 可以得知的是使用S作为后面进行数据交换的对称加解密密码

第八步:密钥协商完成

密钥协商完成,此时再经过第一步到第六步后,服务器和客户端在保证数据全部安全、无人篡改、确定是彼此发送的前提下,神不知鬼不觉地完成了对称密钥的协商,从这里开始客户端和服务器将使用这个对称密钥对彼此发送的数据进行加密和解密,从而保证信道上数据的保密性。

最后一个小问题,关于Sesssion ID。从上面这个HTTPS建立链接的流程来看,这是一个极其耗费时间与CPU性能的流程,所以说对于同一个「客户端和服务器」而言,如果已经建立其HTTPS链接了,就尽量复用,不要反复建立HTTPS链接,这个时候就用到Session ID了,TA的作用就是避免反复多次地建立HTTPS链接。Session ID就是个key,TA在服务器端被保存起来并对应着一个value,这个value就是用于对称加解密的密钥。所以当服务器发现客户端携带Session ID请求时候,就会看是否有该Session ID。如果有,则取出其对应的value,继续进行对称加解密;如果没有,重复?文章里的第五步到第八步。

HTTPS真的是人类安全应用的高峰典范,是跨学科综合应用落地的榜样。大佬们用已有的各项技术在网络信道本不安全的情况下,创造出了一种安全的通信机制,这背后的智慧实在是太厉害了。你我不必言创造,能摸索清楚就已经很不容易了。

如果你觉得有点儿用,感谢点个在看,欢迎分享转发。

0 人点赞