HTTPS 第一次握手
首先,客户端会发送一个 ClientHello 的请求,同时传递SSL握手需要的一些必要参数,主要的字段参数如下:
client_version:客户端支持的TLS版本号 client_random:客户端生成的随机数,是后续对称加密密钥的必要参数之一。 session_id:如果客户端想要重用HTTPS会话,则在连接的时候需要携带此参数,否则为空。 cipher_suites:列举客户端支持的加密套件,也是让服务端选择加密方式的参考。如果session_id 字段不为空(暗示恢复会话请求),则这个字段必须携带。 compression_methods:客户端支持的压缩会话列表,如果session_id 字段不为空(暗示恢复会话请求),则这个字段必须携带。 extensions:扩展字段,为了方便以后扩展新字段,或者协议对接方需要自行扩展协议使用。也就是所谓的留后门。extensions字段TLS1.3重要兼容字段。 扩展字段需要着重记忆,因为TLS1.3的核心部分。
第一次握手以客户端质询服务端是否支持HTTPS为开端,给出一些自己可以提供的参数作为参考,大致的意思是“你好,我想要进行HTTPS连接,请问您这边可以支持么”。
ClientHello 属于子协议handshake的一部分。
另外附带RFC协议的结构体定义,比文字描述来的直白很多:
struct { ProtocolVersion client_version; Random random; SessionID session_id; CipherSuite cipher_suites<2..2^16-2>; CompressionMethod compression_methods<1..2^8-1>; select (extensions_present) { case false: struct {}; case true: Extension extensions<0..2^16-1>; }; } ClientHello;
服务器收到ClientHello请求,如果它可以支持则会回送ServerHello 响应内容,同样需要回送相关字段,表示自己认识这些内容:
server_version:为了向后兼容,服务端会根据客户端的 client_version 选择合适的以协议SSL通信。 server_random:服务端独立于client_random 独自生成。也是加密密钥的关键参数。 cipher_suite:默认从cipher_suites选择合适的加密套件。 compression_method:从客户端传递的压缩算法列表,同样从中选择合适算法。 extensions:扩展内容 ServerHello 也属于子协议handshake的一部分。同样是结构体定义,比较直观:
struct { ProtocolVersion server_version; Random random; SessionID session_id; CipherSuite cipher_suite; CompressionMethod compression_method; select (extensions_present) { case false: struct {}; case true: Extension extensions<0..2^16-1>; }; } ServerHello;
服务器收到请求之后会进行相关答复,他对客户端说“你好,我支持XX协议,我看到了你给我的信息,经过考虑我将使用XX加密算法,XX压缩算法…”
第一次握手是试探性的问候一些双方是否可以支持HTTPS,是一种高效验证手段。
HTTPS 第二次握手
第二次握手是服务端开始的,ServerHello发送完成之后接着他需要传递 Certificate(非对称加密公钥) +数字证书给客户端进行CA认证。
CA 认证
在CA认证的步骤中,首先是服务端如何组织证书,这里又回到X509 V3的证书格式,可以大致总结出数字证书的关键字段:
Version:X509证书版本,目前大部分都是 3这个数字; Serial Number:每个CA生成的数字证书都需要有一个唯一序列号。这个序列号的意义在于一旦某个数字证书被破解,也无法破解同类型的数字证书,另一方面是方便标识证书; Issuer:表示证书的颁发者。遵循X509格式; Validity:证书的有效开始时间和截止时间。过期需要续费; Subject:证书的描述的实体,比如B站是上海幻电; Subject Public Key Info:CA证书的公钥加密算法;
你可能会问这些信息你咋知道的呀,其实也是浏览器的CA证书里面看的,没什么神奇的:
有了证书之后,接着是是对上面的数字证书内容做摘要算法生成一个数字签名,然后CA再用自己的私钥给数字证书的摘要套一层私钥加密,最后再交个客户端。
下面是整个数字证书和签名认证的图例:
Certificate 在图中所属的位置是数字签名,实际上Certificate 还需要加到数字证书上进行传输才算是完整的。
注意:随着 密钥交换方式的加强,Certificate 在算法中还会再套一层“迷彩服”,这些内容会随着密钥交换方式的变化而出现很大的差别。
数字证书传递之后,接下来是关键的一步,也就是密钥交换算法的协商,密钥交换的算法实际上是对于会话密钥(对称加密方法)选择,也就是如何安全的把实际交互用的对称密钥告知服务器。
这里再补充一点,因为客户端通常缺乏客户端证书认证,所以客户端如何把后续要对称加密传输的密钥告知服务器是一件麻烦事情,因为黑客一旦窃取到这个信息前面的交互都前功尽弃了。非对称加密对于加密密钥的保护是一种方案,比如传统的RSA,但是RSA存在很大漏洞,这一点在后续的内容进行解释。
密钥交换算法是指如何安全的进行密钥传输,之前介绍HTTPS利用非对称加密进行数据传输,非对称加密本身也不是百分百安全的,因为 私钥是有可能被破除的,为了更加安全的传输服务器的公钥,密钥交换的手段实际上也在不断改进。
加密套件
介绍下一步之前,这里要补充一下【加密套件】的内容,加密套件涉及了下面几种内容的加密:
摘要算法 会话密钥加密算法 密钥交换算法(服务端非对称密钥加密) 签名算法(CA的非对称加密)
以上面的内容为例,加密套件使用的是 ECDHE_RSA With P-256 and AES_128GCM,指的是:
密钥协商算法使用 ECDHE; 签名算法使用 RSA; 握手后的通信使用 AES 对称算法,密钥长度 128 位,分组模式是 GCM; 摘要算法使用 P-256;
通常情况下握手时密钥交换算法和签名算法都是使用 RSA,但是随着互联网发展,其实RSA存在很大的安全隐患。也就是所谓的前向安全性 问题。
前向安全简单理解是,如果有黑客不断的窃取密钥交换的信息,RSA一旦被破除加密出公钥的私钥公式,那么他可以用这个私钥破除之前所有的消息,获取所有的内容。
也就意味着,如果黑客破开某个大型网站的服务器私钥,那么可以借此私钥对于网站所有其他用户信息进行窃取,这种情况后果不堪设想。
随着量子计算机的发展和不断成熟,传统的加密手段在不断更新换代,密码安全也在遭遇前所未有的挑战。
为了破除RSA的魔咒,目前HTTPS传输的密钥交换算法采用了ECDHE,ECDHE对DH算法进行封装和改进,DH算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换,这个神奇的交换原理完全由数学理论支持,至于怎么支持的,个人也并不是很清楚,就不误人子弟了,可以自行上网查找相关资料。
Server Key Exchange
ECDHE的交换方案我们可以简答理解为:(随机私钥+ 数学推导公式 = 随机公钥 + 非对称加密)。
通过签名的内容我们知道,只要是在网络上传输真实公钥,就是存在安全隐患的,所以现在的密钥交换通常使用ECDHE,HTTPS中Server Key Exchange中选择RSA加密还是ECDHE加密差别是很大的,比如RSA密钥交换算法不会多出Server Key Exchange这个步骤,而是服务端直接用RSA加密对称密钥,然后传给客户端。
这一篇文章以现在流行的ECDHE加密为主,RSA加密的部分已经落伍就不再介绍了。
由于ECDHE本身的复杂性,Server Key Exchange 首先会随机生成一个椭圆曲线的私钥以及随机公钥,这个公钥叫做椭圆函数曲线公钥。
意思是说我选的这个算法很复杂,再给你一个公钥,这个公钥在后面的对称加密密钥获取是有帮助的,但是为了防止这个公钥被拿到,我在公钥上面再用RSA私钥再加密一遍,你拿到后用服务器的公钥解开。
到这里Certificate本身的含义就发生了很大变化了, 原本对于RSA算法来说,只需要把这个值设置为RSA加密后的随机数即可,到这里RSA实际上掩盖的是被数学公式运算过的椭圆曲线公钥(Server Pararms),也就是说RSA加密的是可以被当作是一个随机生成的公钥,这个公钥就可以保证哪怕真的被破解出私钥,也是具备前向安全性的。
这里不好理解,我简化一下:简单理解是狸猫换太子,服务器给客户端的RSA公钥,解开来是加密算法的公钥,但是不能用这个公钥加密,这个公钥只是一个算法的“媒介”。
当然客户端需要小心保管这个算法“媒介”,但是黑客哪怕真的拿到这个媒介,实际上也是没啥用的,这一点同样下文介绍。
ECDHE我们可以理解为,算法在服务器和客户端这两边各给了半片钥匙,要凑齐一个钥匙,需要都有这两个半片钥匙,再搞点特殊的粘合剂粘在一起,才是真正的“公钥”。
哪怕有了数字证书,加密手段还一套又一套。如果想了解这个公式的计算可以看看 3.4 HTTPS ECDHE 握手解析 | 小林coding (xiaolincoding.com)。我数学差就不带大家看了。
CertificateRequest*
在发送Server Done之前,如果本次HTTPS是双向认证,那么客户端此时可以通过CertificateRequest* 索要客户端证书,保证双方向的信息安全。
客户端证书使用较少,个人也没有实验环境,这里不做过多解释,PASS。
Server Done
把上面一套事情做完之后,服务器发送Server Done作为第二次握手的结束标志。接下来就是等待服务器是否认可。
HTTPS 第三次握手
第三次握手首先会收到服务端的证书信息以及 Certificate, Certificate 经过了ECDHE加入的魔法已经很复杂了,所以我们放松一下,先看看服务端证书校验部分。
证书校验过程签名实际前文提到过了,这里简单概括几个关键步骤:
根证书自上到下验证,进一步加强CA证书的可靠性和安全性。 使用CA的公钥解密出证书,然后按照通用的签名算法,对于数字证书做加签比对,任意一方不对等都可以认为请求是否问题的,此时客户端可以拒绝HTTPS通信。 证书信任链,信任链从根证书取出公钥验证下一级证书,直到拿到服务器证书为止。 浏览器内置CA公钥,不经过网路传输,会导致黑客没法做数字证书的手脚。 数字证书认证分为两层,第一层是CA非对称加解密认证,第二层是数字签名形成的“指纹”验证,如果证书有任何改动,指纹都是匹配不上的,如果私钥加密信息被篡改,解开的信息会是客户端无法识别的乱码。
证书验证没问题的情况下,如果服务端上要求Certificate*,客户端也要按照基本要求发送客户端证书+数字签名+客户端公钥,但是这里可以发现有个漏洞,客户端无法证明自己就是自己,为什么会这么说,先别急,我们先看看单向认证如何处理。
我们接着说ECDHE这个复杂的要死的算法,拿数字证书里面的服务端Certificate,取出服务端的随机公钥,注意这里拿到的是非对称加密的公钥,不是密钥交换算法的公钥。
公钥、公钥,非对称加密公钥,椭圆函数曲线公钥,数字证书公钥,客户端证书公钥,哪来那么多公钥!
服务器接着也按照服务端(实际上是ECDHE)的要求,也自己生成一套椭圆曲线的公钥(Client Params),然后用非对称加密的公钥给他加密一遍确保安全,同样发送 ClientKeyExchange传给服务端。
到这一步,客户端此时把两个“半片”钥匙都拿到了,接着就看看服务器能不能知道自己的“暗号”了。
CertificateVerify (客户端认证)*
还记得签名说的服务端的客户端认证么,发送完ClientKeyExchange之后,如果有客户端证书校验,还需要加一步CertificateVerify操作,里面包含了之前交互的所有报文+签名。
这里可能会有疑问,客户端不是有证书么?为啥还要自己再发个指令证明一遍自己是自己,为什么不能像服务器一样放到 ClientKeyExchange里面发给服务器减少交互步骤?
这个问题老外很多年前就提到过,具体看看下面的帖子。这里我把下面的文章回答内容大概看了下,然后按照自己的思路理解一遍:
第一个问题:能够给材料做私钥加密不能证明客户端有对应的证书的私钥。
这里个人举个例子解释一下,比如你在大学上选修课,你上课上到一半想要逃课回寝室打游戏,但是又不能让位置空着,于是你从别的班花钱请了个小弟帮你顶包骗过老师。
这时候老师要怎么证明这个学生看上去是不是不对劲呢?最简单的办法是把这个同学叫起来,问一问他这节课之前讲了哪些内容呀,这种被顶包上去的,一问心里慌了把你供出来了,老师很生气直接说“我的课你也敢逃,那你以后都不用来了,期末成绩直接不及格!”,你心里后悔,要是把之前讲过的课和顶包同学解释一遍,就不会出问题了。
大概就是这么个道理,应该比较形象了吧,嗯。
第二个问题:客户端认证是可选的,和单向认证的相关内容放到一起实际上不太合适,万一哪一天客户端认证火起来就更难办了。
为了不要有过多的双向认证干扰,客户端认证就介绍到这,更多内容可以自行上网搜索。
Change Cipher Spec
做完ECDHE传输之后,我们可以看到目前的情况是这样的,客户端和服务端各自持有 (Client Params) 以及 (Server Params) 这两个公钥,于是它们会开始计算真实的会话密钥。
计算会话密钥之前,有一步随机数生成操作,因为TLS 设计者不信任客户端和服务端任意一方,所以他会要求双方拿着 (Client Params) 以及 (Server Params) 这两个公钥+一顿复杂算法生成出一个随机数,叫做 PreMaster 。
PreMaster实际上依然不是会话密钥,这时候还需要拿到第一次握手传递的 Client Random和 Server Random 在加上PreMaster做PRF(伪随机数函数) 运算,生成最终的MasterKey,也就是会话密钥。
所以会话密钥的最终计算公式如下:
master_secret = PRF(pre_master_secret, “master secret”,ClientHello.random + ServerHello.random)
是不是很复杂,很复杂对了,不复杂黑客就闯进来了。这样的会话密钥黑客基本上是破不开的,因为PreMaster 不走网络传输,都是双方内部用固定算法算出来的。
这个工作方式实际上比较像我们前面介绍的密码机,也就是说哪怕拿到密码也破不开,只有密码机认识这一串密码,而生成密码机的机器在客户端和服务端两边放着。
master_secret叫做主密钥,在RFC中规定是一位固定 48 Bit 的随机数。此外,为再一次确保master_secret 安全性(有完没完!!!),master secret 还不是最终密钥(放过我=-=),还需要再次加上加密套件的 摘要算法,比如这里用的是 AES_128GCM,这样就避免了会话密钥被重复计算的隐患。
双方都有了master_secret和派生的会话密钥,客户端就可以发一个 Change Cipher Spec,告诉服务端我的会话密钥生成了,接下来用“那个”做对称加密吧。
Finished
Change Cipher Spec发送完了,然后再发一个“Finished”消息(Encrypted Handshake Message,所以数据摘要),把之前所有发送的数据做个摘要,这时候再用会话密钥加密一下,让服务器做个验证。
为啥还要在做摘要再验一遍,这里应该比较好理解了,因为按照ECDHE密钥交换算法,最后这个摘要肯定是只有服务端解得开的,中间被窃取是不可能被破解的。
同样的,这个指令也是一个“测试”,如果对方解不开,肯定也是存在猫腻的,无形中又多了层安全网。
这时候你是黑客,你一定是???你们咋就会话加密了?你们的算法呢?你们的交流呢?咋就开始密文传输呢?
HTTPS 第四次握手
走过万里长征,最后的交互就简单了。服务器收到客户端的回应,注意这时候传过来的已经全是外人看不懂的密文了,我们抓包看到的也是一堆乱码,这时候服务端通过自己生成的会话密钥解一下密文,然后也按照客户端的Change Cipher Spec和Finished来一套,让客户端也确认一下。
两边都核实无误,第四次握手就完成了,可以看到第四次握手是很简单的,简单的确认操作。
最终HTTPS连接构建完成,客户端开开信息上网,服务端安安心心传数据,黑客无可乘之机。
小结
我们遍历了整个HTTPS TLS1.2 的历史发展和设计,可以看到HTTPS本身虽然细节很多,但是掌握大体内容之后并不是特别难,重点部分在于理清对称密钥加密是如何计算,也就是目前主流 ECDHE密钥交换算法的一些大概流程,还有CA数字认证的这一个关键过程。
需要注意的是整个HTTPS的交互过程是非常灵活的,除非某些关键步骤之外,其他的子协议是类似“插件”一般可选的,虽然我们传统意义上认为HTTPS就该是有CA认证,就该有非对称加密和算法,实际上设计上而言这些东西都无关紧要。
从官方给的案例来看就是下的情况:
上面这张图的简化流程可以给大伙比喻成一段话:Hello,你好,请使用密文传输,Over,好的,使用密文传输,Over,哔哔哔哔哔…,哔哔哔哔哔…。实际上HTTPS就是通过这一段话不断扩展出来更多细节的。
之所以设置的这么灵活,个人认为是设计者不想把流程设计的那么死,另一方面是由于子协议自身模块化的思想决定了不能把所步骤当成一个整体来看待。
不过话说回来,现实情况是TLS协议本意是灵活扩展,但是TLS1.2经过长达数十年互联网膨胀,已经牵一发而动全身,TLS1.3的升级也因为历史发展问题,不得不对TLS1.2做出让步和妥协。
值得一提的是,这一节HTTPS建立连接的过程没有以RSA作为密钥交换算法介绍,而是介绍现在真正主流的ECDHE(因为RSA在TLS1.3宣布禁止使用RSA算法),这算法很复杂,要真正完全理解需要比较强的数学功底,但是我们跳过数学的部分,重点介绍了在HTTPS中的作用,个人把它形象理解为两个半片钥匙,通过魔法合成魔法门,这三个内容搭配成随机摘要算法的“根”,最后再用“根”生成出强随机和安全性的会话密钥。
还有令我感到亮眼的地方是,密钥交换算法ECDHE在双方各有“暗号”的情况下,会根据暗号生成一个新的难以破解的暗号,这进一步加强了会话密钥的安全。
ECDHE最大功劳是真正会话密钥的交换不需要网络传输,而是通过数学公式推导算出来的,这最大程度保证安全性。当然是在量子计算机还没到来之前。
从软件设计思想来看,协议制定和软件更新换代通用面临向后兼容问题,没有完美的标准,只有不算完美的前后兼容。