赞
踩
目录
5. 释放TCP连接 (当connection为close时)
7. 发送HTTP请求 (当超文本中有如图片等其他资源的指针)
9. 释放TCP连接 (当connection为keep-alive时连接超时后)
在之前的文章中,我们了解了传输层和网络层和其中主要的协议;知道了网络层是如何进行寻址的,而传输层又是如何传输数据的;也学习了传输层暴露给应用层的接口,也就是socket,以及它的使用方法。在描述了以上内容后,本文将重点讲解应用层中最常用的三个协议:DNS协议、HTTP和HTTPS。但在讲解具体的协议之前,需要对因特网中常用的URL进行简单的介绍。
在日常生活中我们经常能够听到URL,而作为一个开发人员,应该也会比较频繁地听到URI,而URN这种说法则使用的比较少。虽然名字相似,只有一个字母不同,但他们所代表的意思和作用却有很大的差异。
URI的全称是Uniform Resource Identifier,它提供了一种识别资源的方法,即给予某个资源一个特定的标识,而这个标识是唯一的,或者说至少在其所属的命名空间中是唯一的。在2005年1月发布的RFC 3986中对URI格式的描述大致如下:
- URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
-
- hier-part = ("//" authority path-abempty)
- / path-absolute
- / path-rootless
- / path-empty
-
- authority = [ userinfo "@" ] host [ ":" port ]
-
- path = path-abempty ; begins with "/" or is empty
- / path-absolute ; begins with "/" but not "//"
- / path-noscheme ; begins with a non-colon segment
- / path-rootless ; begins with a segment
- / path-empty ; zero characters
URL的全称是Uniform Resource Locator,它是URI的一种,或者可以说URL是URI的子集之一。而URL除了能标识资源外,还可以通过描述资源的主要访问机制提供一种定位资源的方法。根据1994年12月发布的RFC 1738中的定义,在不同scheme下的URL的具体格式各不相同,但所有URL的基本格式如下:
URL = scheme ":" scheme-specific-part
因为URL是URI的子集,所以URL的格式必须遵循URI的规则。在RFC 1738中写道,当使用常见的因特网scheme时,URL的格式如下:
- scheme = common internet scheme
- scheme-
- specific- = "//" [user ":" password "@"] host [":" port] ["/" url-path]
- part
HTTP是非常常见的使用URL的一个应用层协议。在使用HTTP时,URL的scheme为”http“,而scheme-specific-part中的userinfo部分缺省,同时在path后增加了searchpart部分,也就是所谓的query字段。此时URL的具体格式如下:
- scheme = "http"
- scheme-
- specific- = "//" host [":" port] ["/" path ["?" searchpart]]
- part
URN的全称是Uniform Resource Name,它同样是URI的一个子集,指的是scheme为“urn”时的URI。在1997年5月发布的RFC 2141中定义的URN的格式如下:
- URN = "urn:" NID ":" NSS
-
- NID = Namespace Identifier
-
- NSS = Namespace Specific String
DNS协议的作用与之前提过的ARP类似,只是ARP负责在IP地址和MAC地址之间进行转换,而DNS协议则负责域名和IP地址之间的转换。通过之前的内容,我们知道了HTTP需要使用URL来进行对特定资源的访问,而我们日常使用的URL中的host部分通常不是IP地址而是域名,并且也很少在URL中指定端口。在使用一些如HTTP等协议时,都有其对应的默认端口,不需要手动指定,而通过域名获取IP地址则由DNS协议负责。
通过之前的学习,我们知道IP地址是主机在因特网的网络层及以上层次中使用的地址。在使用IPv4时,主机的IP地址由32位二进制数组成,换成点分十进制也需要由4个0到255之间的数来表示,而使用IPv6时的地址甚至达到了128位,非常不方便记忆。因此,人们发明了域名,方便用户记忆,比如www.baidu.com就是百度的域名,对应的IP地址则是202.108.22.5。最初的域名和IP地址的对应关系全都保存在hosts文件中,由互联网信息中心来管理。如果需要发生变更,如新加入一个主机,或是某个主机的IP地址需要变更,都需要到互联网信息中心申请变更hosts文件,而其他计算机也需要定期进行更新才能访问变更后的主机。但是这样效率太低了,因此就出现了域名系统,也就是DNS,而DNS协议也被用于通过域名获取相应的IP地址。
域名在分配的时候与IP地址相同,必须是唯一的。为了实现这种唯一性,就需要在命名的时候使用分层结构。
就像百度的域名www.baidu.com一样,DNS域名是分级的。而与我们日常的阅读顺序不同,域名的级别是从右到左依次降低的,通常由点号隔开。就以百度的域名为例,其中的com是一个顶级域名,代表这是一个企业域名,常用的还有net代表的网络运营商域名和org代表的非营利组织域名等;而baidu则是一个二级域名,这里直接用了公司的名称;www是一个三级域名,原本指代万维网 (World Wide Web) 提供的需要用浏览器进行访问的网页服务,但目前随着浏览器功能逐渐拓展,慢慢失去了曾经的作用,变成了一种习惯用法。通常,在二级域名之后的最低级的域名,如www.baidu.com中的www,以及微信文件传输助手的域名filehelper.weixin.qq.com中的filehelper,代表该域名指向的服务器所提供的功能,与此相似的还有一般由mail代表的邮件功能等。
和域名类似,DNS域名服务器也有其对应的层次划分,从上到下依次分为根域名服务器、顶级域名服务器、权限域名服务器和本地域名服务器。根域名服务器是最高层的域名服务器,管理与顶级域名服务器对应的顶级域名;顶级域名服务器负责管理其对应顶级域名下注册的二级域名;权限域名服务器负责一个区域内域名的解析工作;而本地域名服务器则负责处理DNS客户端发来的请求,约有80%的DNS请求直接由本地域名服务器完成解析,并将IP地址返回给客户端。
在了解了DNS的层级结构之后,我们就可以大致分析出通过DNS协议进行域名解析的流程。但需要额外注意的是,DNS协议提供的域名服务主要是基于UDP实现的,使用的端口号是53,而查询具体IP地址的方式可以分为递归查询和迭代查询。对于这两种查询方式的使用会在下文中具体分析,以下是从一台主机发出DNS请求获取IP地址的流程。
当一台主机需要通过域名获取地址时,通常会首先在浏览器的缓存中查询对应的记录。如果浏览器的缓存中不存在该记录,则会到主机的缓存中,也就是hosts文件中,查询对应的记录。如果主机的缓存中同样没有该域名对应的记录,便会通过主机内的DNS客户端向本地域名服务器发出DNS查询请求。如果本地服务器中有该条记录则直接跳到步骤6,否则进行下一步。
当本地服务器中不存在对应的记录时,本地服务器会向根域名服务器发出查询请求。到这一步为止的查询操作属于递归查询,也就是说,当本地服务器需要发出查询请求时,会自己以DNS客户端的身份向根域名服务器发出查询请求,而不是让主机去向根域名服务器查询。收到请求后,根域名服务器会将下一个应该查询的顶级域名服务器的IP地址返回给本地域名服务器,此时流程进入到下一步。
收到顶级域名服务器的IP地址后,本地域名服务器同样自己以DNS客户端的身份向顶级域名服务器发出查询请求。因为与上一步的查询过程相同,发起此次查询请求的DNS客户端同样是本地域名服务器,所以这种查询方式被称为迭代查询。而接收到请求的顶级域名服务器也会向本地域名服务器返回需要查询的权限域名服务器的IP地址,并推进到下一步流程。
同样使用迭代查询的方法,本地服务器通过收到的IP地址向权限域名服务器查询所需域名对应的IP地址。到了这一步,如果一切顺利,通常整个查询的流程就完成了,权限域名服务器通过解析域名得到了对应IP地址。此时流程会进入到IP地址的返回阶段,也就是以下两个步骤。
在这一步中,解析域名获得IP地址的权限域名服务器会将与域名对应的IP地址返回给本地域名服务器。此时的本地域名服务器通过三次的迭代查询,成功获得了所需的IP地址,并能够将IP地址返回给发出请求的主机,也就是最后一步。
到这一步时,本地域名服务器已经完成了迭代查询的过程,可以将目标IP地址返回给主机来结束递归查询,而获得了IP地址的主机则可以使用它向对应的主机发出请求。
在了解了如何用DNS协议通过域名获取IP地址后,就可以使用该IP地址和URL中的其他信息来发起HTTP请求了。HTTP的全称是HyperText Transfer Protocol,意为超文本传输协议。而这其中的超文本则是指除了文本信息之外,还有指向其他内容的指针。而超文本有许多不同的格式,其中最常用的格式是HTML。
HTTP是一种使用客户/服务器模式、基于请求与响应、无状态的应用层协议,一般使用TCP进行数据传输。在较早的版本中,长连接是默认关闭的 (connection的值默认为close),所以每一次请求都需要进行建立TCP连接和断开TCP连接的操作。而根据之前提到的,超文本除了携带文本信息之外,还包含指向如图片等其他内容的指针。当浏览器发现这些指针时,会再次发起请求,就会导致显示一个网页时需要连续多次建立TCP连接,会造成对资源的浪费。因此从HTTP/1.1起,长连接被设置为默认开启状态 (connection的值默认为keep-alive)。但是长连接只是允许多个请求使用同一个TCP连接,在使用TCP连接的时候仍然只能通过半双工通信的方式,即同一时间只能处理一个请求或响应,其他的请求或响应都是被阻塞的状态,所以同一时间数据的流向仍然是单向的。为了进一步提高效率,2015年发布的HTTP/2中引入了多路复用的技术,即可以通过单一的TCP连接连续发送多个请求,通过流机制同时处理多个请求或响应,而不会发生阻塞。当客户端与服务器端建立传输层连接后,会使用相应的方法向服务器端发起请求,收到请求的服务器端则找到合适的资源返回给客户端。
图1. HTTP请求报文和响应报文
HTTP的请求和响应报文的格式可以参考图1,在请求报文中有请求行、首部行和实体主体,而响应报文则是将请求报文中的请求行替换成状态行。请求行/状态行只占报文的第一行,和首部行之间用换行符进行分隔;首部行的每个字段都是一个键值对,两个字段之间用换行符进行分隔;而首部行的最后一个字段和实体主体之间则用两个换行符,即一个空行进行分隔。请求报文中的请求行包含三个字段:方法、URL和HTTP版本;响应报文中的状态行同样包括三个字段:HTTP版本、状态码和状态信息。HTTP请求报文中常见的方法有GET、POST、HEAD、PUT和DELETE,以下会对每个方法的作用和特点进行简单的描述,而响应报文中的状态码和状态信息的定义以及规则可以参考图2。
图2. HTTP响应报文的状态码和状态信息
GET方法主要用于请求URL指定的资源,因此实体主体中不包含信息,而服务器端在收到请求并成功处理后会将响应的资源携带在响应报文的实体主体中返回给客户端。虽然使用GET方法时请求报文的实体主体为空,但是仍然可以通过URL的query部分来携带参数。
POST方法主要通过请求报文中的实体主体传输数据,而服务器端在接收数据后会进行相应的处理,并将处理后的结果返回给客户端。虽然GET方法可以通过URL携带参数,但是由于URL的长度受限,因此携带的数据量较小,而POST方法则没有这个限制。另外,POST方法由于是通过其实体主体传输数据的,不像使用URL时一样必须明文,因此POST方法会比GET方法更安全。同时由于功能设计的原因,GET方法的功能是获取资源,因此并不能对资源进行修改,而使用POST方法时,服务器可以通过其提交的数据对资源进行修改后再返回。
HEAD方法的使用与GET方法相同,只是使用HEAD方法时服务器端并不会返回资源。HEAD方法主要用于确认URL的有效性或是其指向资源的更新时间等信息。
PUT方法主要用于由客户端向服务器端上传资源,需要客户端将资源包含在实体主体中,并由服务器端将资源保存到URL指定的位置。
DELETE方法与PUT方法的功能相反,是由客户端发出请求让服务器删除保存在URL指定位置的资源。
之前提到,HTTP是一种无状态的应用层协议。但有的时候服务器端需要针对特定的状态,如用户已登录时,进行相应的个性化展示,此时就需要用到Cookie和Session。其中Cookie通过在客户端记录信息来确认用户身份,而Session则是通过在服务器端记录信息进行用户身份确认。
Cookie的实现首先需要浏览器的支持。在浏览器支持该功能的情况下,服务器端可以给客户端发送一小段文本信息,也就是cookie,用于记录用户状态,由浏览器进行管理。保存cookie后,当浏览器下次访问该网站时,浏览器会将cookie与原请求一起提交给服务器端。服务器端可以根据收到的cookie来辨认用户身份和状态,在有需要时也会做一些相应的修改并反馈给客户端。浏览器会对不同网站的cookie分别进行管理,通过域名进行区分。不同域名的网站之间不能相互操作对方的cookie,即使网站使用的是服务器 (如www.google.com和images.google.come都属于Google,但由于域名不同,因此双方都不能操作对方的cookie)。Cookie的最大存活时间是由服务器端进行设置的,对客户端是只读的。最大存活时间的默认值为-1,当其值为正数时,代表该cookie最长可存活的时间;当其值为负数时,该cookie为临时cookie,并不会被写到Cookie文件中;而当其值为0时,则表示浏览器需要删除该cookie。事实上,cookie机制中并没有删除功能,因此通过将存活时间归零来时Cookie即时失效。
Session是由服务器端直接对客户端状态进行记录,比cookie在使用时更为简单,但也增加了服务器端储存上的开销。服务器端在客户端第一次访问网站时创建session,并将用户信息保存到session中。同时,服务器端也会将用户的最后访问时间也保存到session中,并且在每次用户访问时进行更新。为了使用起来更高效,通常服务器会将session放到内存中,因此为了应对大量客户的同时访问,session中的内容会尽量精简。Session同样具有有效期,通过session的超时时间来控制,一旦客户端超过超时时间都没访问网站,session便会自动失效,同时也可以手动使session失效。虽然session本身由服务器端进行保存,但通常为了使其能够正常运行,仍然需要cookie的支持。因为HTTP本身是无状态的,因此cookie会被作为标识来使服务器端能够正确定位到具体的session。当浏览器不具备cookie功能或其cookie功能被禁用时,就需要通过URL地址重写的方式来使用session,其本质就是将session的标识重写到URL中,并由服务器端进行解析。
两者最主要的区别就是从cookie是由客户端浏览器进行储存和管理的,而session则是储存在服务器端的。由于储存位置的不同,session本身是无法篡改的,只有当session标识被第三方获取后,第三方才能伪装成客户端发送请求,因此比cookie的安全性更高,但cookie也可以通过加密来解决其安全性问题。在不考虑有效期等其他因素的情况下,session的生命周期跟随其对应的浏览器页面,当页面被关闭时,session也被关闭了;而cookie的生命周期则是会以文件的形式永久保存在本地。
在了解了相关的各种信息之后,包括在之前的文章中对传输层和网络层的介绍,我们现在可以对浏览器在输入URL后的完整工作流程进行一个总结,其大致流程如下:
在浏览器中输入URL后,浏览器会从URL中提取出域名,之后通过DNS协议获得与域名对应的IP地址。
获取到IP地址后,浏览器会以客户端的身份与服务器端的80端口 (默认的HTTP端口) 通过三次握手建立TCP连接,并生成对应的socket。
在成功建立TCP连接后,浏览器会发起一个HTTP请求,具体的操作是将HTTP请求报文推入socket中,并通过TCP连接发送给服务器端。
服务器端在收到HTTP请求报文后,会从其携带的URL中解析出目标超文本的路径,并根据该路径定位到具体的文件。在定位到具体的文件后,服务器端会将该文件写入到HTTP响应报文的实体主体中,并将该报文推入socket返回给客户端,也就是浏览器。
当服务器端返回的HTTP响应报文传输完成时,如果未开启长连接,则关闭TCP连接;而如果开启了长连接,则在数据传输完成后启动超时计时器。
浏览器在收到服务器端返回的HTTP响应报文后,会首先对其携带的状态码进行检查。如果状态是成功,浏览器则对报文中携带的超文本进行解析并显示。
当超文本中有指向其他资源的指针时,浏览器会再次向服务器端发起HTTP请求获取资源。如果此时长连接是打开的,并且该连接还未超时,则该请求会通过之前建立的TCP连接进行发送;如果此时连接是关闭的状态,则需要重新建立一个TCP连接。
当服务器端收到浏览器发来的上述请求后,会以与之前同样的方式定位到具体的文件,并通过响应报文发送给浏览器。
当浏览器发出的所有请求都得到响应后,客户端与服务器端之间的数据传输就停止了。此前每次数据传输完成之后,长连接都会重置计时器。当双方的静默期超出了预设的时长后,该TCP连接仍然会被关闭。
现在我们了解了HTTP,也知道了浏览器是如何使用URL最终展示网页的。但是互联网在设计之初,并没有考虑到安全问题,而是默认所有互联网的用户都是可以互相信任的。然而,随着互联网的逐渐普及,许许多多的安全问题也渐渐暴露出来,所以使用HTTP的纯明文的通信也就变得不再可靠。为了满足安全性的需求,出现了HTTPS。HTTPS的本质是在应用层和传输层之间增加了一个安全层,即SSL,后更名为TLS。在讲解其具体实现之前,需要首先对加密原理进行一个简单的概述。
HTTPS涉及到的加密算法主要分为两类,分别是对称加密和非对成加密。这两类加密算法各有各的优势,也分别存在各自需要解决的问题。
对称加密是一种在加密和解密时使用相同的密钥的加密策略,其主要优势就是速度快,因此通常在需要传输大量数据时使用。其中密钥是控制加密和解密的指令,就好比一把钥匙;而加密算法则是使用这种指令的规则,就好比是使用钥匙锁门和开门的方法。由于加密和解密使用的是同一个密钥,所以在使用对称加密时,相比于算法本身,如何安全管理密钥才是最为重要的任务,因此对称加密的主要问题就是如何将密钥安全地送到对方手中。
非对称加密是一种在加密和解密时使用不同密钥的加密策略,这些密钥都是成对出现的,由一个密钥加密的内容只能通过另一个密钥进行解密,反之亦然。每对密钥都分为公钥和私钥,其中公钥是对外公开的,而私钥则只有生成密钥对的一方持有。这种方式可以解决对称加密中安全传输密钥的问题,但是因为公钥是对外公开的,因此在数据传输时,只能由持有公钥的一方确认私钥持有者的身份,而私钥持有者却不能反过来通过该密钥确认对方的身份。
而且非对称加密,由于公钥的公开属性,存在中间人攻击的问题,即第三方可以截获发送方发送的数据并用其公开的公钥进行解密。与此同时,第三方也可以生成自己的公私钥对,并在将自己的公钥发送给接收方后,伪装成原发送方。当第三发用自己的私钥加密数据发送给接收方时,接收方会误认为发送该数据的第三方是原发送方,并使用第三方的公钥对消息进行解密,同时也用第三方的公钥将数据加密并发出。第三方在收到原接收方发来的数据后,会用自己的私钥对数据进行解密,之后再使用原发送方的公钥对消息进行加密后发送给原发送方。
HTTPS对以上两种加密策略存在的问题的解决办法是使用混合加密,即使用非对称加密来安全地交换对称加密所使用的密钥,再使用对称加密进行数据传输。但在此之前,我们需要对CA证书做一个简单的了解。
为了解决中间人攻击的问题,CA证书应运而生。CA证书是由权威机构颁发给认证用户的数字证书,其中包括的主要内容有颁发证书的机构、证书的有效期、认证用户的公钥、证书的持有者等各种相关信息。而这些信息则会通过指纹算法,即消息摘要算法 (如MD5),计算出一个统一长度的指纹。指纹则会通过签名算法 (如RSA) 使用该机构的私钥进行加密,最终得到一个签名并附带在证书中。签名算法和指纹算法也会被携带在证书中,而收到证书的一方可以通过指纹算法计算出证书的指纹,并从安装在系统中的根证书中获取到颁发机构的公钥对证书的签名进行解密。当解密获得的指纹与计算得到的指纹相同时,证明该证书中的信息完整且未被修改。
有了CA证书后,私钥持有者的身份认证问题得到了初步解决,此时我们需要通过这个单向保密的非对称加密连接来协商一个用于对称加密的密钥,从而最终实现双向加密通信,因此就需要用到密钥协商算法。常见的密钥协商算法会在下文中进行简单的讲解。
在完成密钥的协商之后,HTTPS可以通过相应的对称加密算法进行安全的数据传输。主要的对称加密算法包括DES算法、3DES算法、TDEA算法、Blowfish算法、RC5算法和IDEA算法,各种算法的具体实现在这里不多做描述,感兴趣的朋友可以自行去查阅资料。
在通过HTTPS进行数据传输时,除了加密和解密之外,SSL/TLS会对数据进行一些其他的操作,其中就包括添加MAC。MAC的作用就是让接收方能够检验数据的正确性和完整性,而根据百度百科的描述,MAC算法的本质是一个带加密功能的哈希函数。MAC算法中使用的密钥与对称加密算法相同。
目前常见的密钥协商算法主要分为两类,即RSA算法和DH算法,而DH算法又分为static DH算法、DHE算法和更为细分的ECDHE算法。每种算法有每种算法的优点,也有其局限性,目前理论上在最能保证安全的同时又具有不错的性能的算法是ECDHE算法。
RSA算法一直是最广为使用的非对称加密算法,其算法本身非常可靠,而且使用的密钥越长越难破解。具体的数学理论部分并不影响对将RSA作为协商算法时的理解,因此在这里就不多做描述了,主要就使用时的思路做一个梳理。在一开始,客户端和服务器端都会分别选择一个随机数,这两个随机数被称为Client Random和Server Random。而在进行了必要的身份认证后,客户端会再选一个随机数作为预主密钥 (Pre Master Secret),并通过非对称加密传送给服务器端。正常情况下,就算在这期间的所有信息都被第三方所截获,由于只有服务器端持有私钥,所以预主密钥并不会被破解。此时双方都持有三个随机数,因此可以通过基于MAC算法的伪随机函数 (PRF) 求得主密钥 (Master Secret),并最终生成会话密钥 (Session Key)。因为RSA算法的特性,算法本身的可靠性的确很高。但由于在协商过程中只有预主密钥是加密的,因此当服务器端的私钥泄露后,之前所有传输中使用的预主密钥都会被破解,因此不具备前向安全性。
实际上,DH算法是在1976年提出的,是最早的非对称加密算法。而RSA算法则是在非对称加密算法的思路被提出后,在1977年发明的算法。目前最早的DH算法,也就是static DH算法,已经被放弃,但是因为它是后续算法的前身,在这里也进行一个简单的介绍。其实DH算法的思路比较简单。首先,服务器端的公私钥是固定的,并且经过了CA证书的认证。此时客户端持有服务器端的公钥,而客户端自身根据特定的算法生成自己的公私钥对,并保证通过己方私钥和对方公钥算出来的预主密钥和通过己方公钥和对方私钥算出来的结果相等。在这之后,客户端将自己的公钥发送给服务器端,让服务器端能够计算出相同的预主密钥。在DH算法中,虽然预主密钥是由双方自行计算得出的,但是由于使用的服务器端的公私钥对是静态的,第三方有机会通过截获大量通信数据暴力破解服务器端的私钥。而当服务器端的私钥泄露时,第三方仍然可以通过计算获得预主密钥,所以DH算法同样不具有前向安全性。
为了解决DH算法不具有前向安全性的问题,出现了DHE算法。在DHE算法中,通信双方的公私钥对都是临时生成的。服务器原本的私钥只参与发送临时公钥时的签名,并不参与密钥的协商。因此就算服务器私钥泄露,可能产生的唯一问题只有服务器端发送公钥时的签名可以被伪造,从而导致临时公钥可以被篡改。但此时第三方能够获取到的信息只有双方在打招呼阶段选择的随机数和双方的临时公钥,并不能够从中计算出预主密钥,所以客户端和服务器之间加密的数据也都不会被破解。而对以上信息的任何改动也都会导致计算出来的预主密钥不同,从而在检验阶段发现异常。再退一步说,就算某一次通信中任何一方的临时私钥泄露导致密文可以被破解,由于使用的是仅限本次通信的临时密钥,第三方也无法破解其他传输中的密文,因此DHE算法具有前向安全性。但是因为DHE算法需要涉及到更多的计算,因此效率不佳。
为了提高DHE算法的效率,ECDHE算法被提出了。ECDHE算法本质上也属于DHE算法的一种,只是ECDHE算法*利用的是ECC椭圆曲线的特性来提高计算效率。ECDHE算法在使用前还需要协商使用的椭圆曲线种类,具体协商的时机和协商加密套件时类似,在后面讲解SSL/TLS握手流程时会讲。在使用ECDHE时,双方会首先选择一个随机数作为私钥,并将该随机数与服务器端指定的基点相乘获得公钥,而双方使用己方私钥与对方公钥相乘所得的点的X坐标相等,即为该次连接的预主密钥。
在介绍为所有加密相关的内容后,终于能够讲解HTTPS的安全层本身,也就是SSL/TLS了。SSL最早时由NetScape公司在1994年提出的,但其协议的1.0版本并不完善所以并未公开。后来在1995年和1996年公开了协议的2.0版本和3.0版本,其中3.0版本得到了大规模的应用,直到2015年才被弃用,2.0版本则在2011年就被弃用了。到了1999年,SSL被IETF纳入标准化,并将SSL更名为TLS,但TLS协议的1.0版本相比SSL协议的3.0版本几乎没有改动。此后在2006年,TLS v1.1发布了,修复了前版本的一些bug并增加了一些参数。直到2008年提出的TLS v1.2,其中有了更多的拓展并改进了算法,使其彻底普及了开来。而2018年才纳入标准的TLS v1.3,相较于前版本有较大的改动,对握手流程做了改善,提高了效率,减少了时延。其他改动包括但不限于废弃RSA密钥交换算法并且只采用完全前向安全的密钥交换算法、用AEAD代替MAC成为唯一的加密选项、废弃了一些加密组件和哈希算法、不允许对加密的内容进行压缩、不允许双方发起重协商、取消了改变密码标准协议 (Change Cipher Spec Protocol)、减少了握手阶段的明文以及引入了新的密钥协商机制PSK等。由于TLS v1.3目前仍未普及,并且改动得更为复杂,想要深入了解的朋友可以在了解了TLS v1.2后去阅读这篇文章,也可以选择直接阅读标准文档RFC 8446,这里就重点只对使用更加广泛的TLS v1.2进行描述。
参考RFC 5246,TLS协议主要分为两层:位于下层的记录协议 (TLS Record Protocol) 和位于上层的握手协议 (TLS Handshake Protocol)。但使用记录协议的协议,除握手协议外,还有报警协议 (Alert Protocol)、 改变密码标准协议和应用数据协议 (Application Data Protocol)。记录协议在发送端和接收端分别负责实际数据的分片/重组、压缩/解压缩、加密/解密、添加/检验并移除MAC以及首部的操作;握手协议主要负责建立安全的连接,即所谓的会话 (session);报警协议负责在出现异常时向对方发出警报,如发现消息被篡改;改变密码协议负责通知对方需要改变加密参数,如上文所述,在TLS v1.3中已被取消;而对于应用数据协议,暂未找到相关的词条资料,根据文档中Section 10的描述分析,疑似指代连接中传递的具体数据。文档中仅提到TLS协议中下层的记录协议是位于传输层协议TCP之上的协议,而TLS协议本身则向应用层提供服务,因此对于TLS协议目前的共识是其位于应用层与传输层之间。而在我个人的理解中,根据TCP/IP模型和五层模型对各层的定义,TLS协议必定是高于传输层的,而加密和解密等操作是由应用层决定的,因此在这两个模型中,TLS协议应属于应用层;而根据OSI参考模型中对应用层和传输层之间的两个层次的定义,TLS协议确实属于这两层,但又由于实际的加密和解密之类的操作都是由记录协议完成的,因此不能简单地断定TLS协议中的上层和下层协议分别对应表示层和会话层,所以只能认为TLS协议同时属于表示层和会话层,无法进行更进一步的拆分。而位于上层的握手协议是TLS协议中最关键的一个协议,只有成功完成了握手协议规定的所有流程,通信双方才能进行安全的数据传输。使用不同的密钥交换算法,或是为了满足不同需求,握手协议中的流程都会略有不同,但大体上都是通过四次握手来实现的。下面是对该流程的一个简单的综述,也可以参考SSL协议的3.0版本的标准文档RFC 6101的Section 5.5中展示的流程。需要额外注意的是,每次握手不一定只会发送一条消息 (虽然与报文相同,在标准文档中使用的单词也是message,但由于报文通常是指应用层封装后的数据,而TLS协议则在报文的基础上再进行相应的处理,因此我更倾向于将其直译为消息),也有可能会发送多条。
1) Client Hello
在TCP连接建立后,客户端会向服务器端发起加密通信的请求,即一条Client Hello消息。该消息的内容主要包括:客户端想要使用的协议版本 (如TLS v1.2)、客户端选择的一个随机数 (Client Random,握手阶段的第一个随机数)、会话ID (主要用于会话复用,如果是新建会话则为空)、客户端支持的密码套件列表 (每个密码套件都包括密钥交换算法、对称加密算法和MAC算法)、客户端支持的压缩方式列表以及其他扩展 (如ECDHE需要客户端提供其支持的ECC椭圆曲线列表)。此时客户端未持有服务器端的公钥,所以该消息是明文发送的。
1) Server Hello
当服务器端收到客户端的请求后,首先会确认是否支持请求中的TLS版本,然后会从请求中附带的密码套件列表中选择一个密码套件。此后服务器端会向客户端返回一个Server Hello消息,其中主要包括:服务器端确认的协议版本、服务器端选择的一个随机数 (Server Random,握手阶段的第二个随机数)、会话ID (当客户端携带的会话ID有效时会进行会话复用并返回同样的会话ID,否则返回客户端选择的会话ID,或者直接返回空表示不会缓存会话且不允许会话复用)、服务器端选择的密码套件、服务器端选择的压缩方式以及其他拓展 (如使用ECDHE时,会附带服务器端选择的ECC椭圆曲线)。 此时客户端仍然未持有服务器端的公钥,所以该消息仍然是明文的。
2) Server Certificate
服务器端为了证明自己的身份,会向客户端发送一条该消息,其中包含其证书。如果该证书是由中间机构颁发的,则会将中间机构的证书放在服务器端证书之后,形成证书链。证书链以同样的形式进行延申,直到最后一个证书的颁发机构为根证书。
3) Server Key Exchange
当密钥交换算法是RSA或static DH时,服务器端不会发送该消息。 而当密钥交换算法是DHE或ECDHE时,服务器端会首先选择一个随机数作为此次会话的临时私钥,并计算出临时公钥后发送给客户端。该消息包含计算公私钥所需的信息 (如使用ECDHE时会发送选择的基点)、服务器端计算的临时公钥、指定的消息摘要算法和签名算法以及计算得到的签名。服务器端在计算签名时使用的是其原本的私钥,DHE算法和ECDHE算法只有在这一步中会使用到这个私钥。
4) Certificate Request
有时为了安全起见,服务器端会要求客户端证明自己的身份,即要求客户端发送一份自己的证书供服务器端验证,便会向客户端发送一条该消息。
5) Server Hello Done
发送该消息的目的是告诉客户端:服务器端已经发送完所有需要发送的内容,打招呼的阶段已结束。发送完该消息后,服务器端会开始等待客户端的响应。在收到该消息后,客户端会首先对服务器端提供的证书进行认证。如果存在证书链,服务器端则会首先找到相应的根证书,并从后往前完成认证。而在顺利完成所有认证之后,如果有必要,客户端会验证第二次握手中的参数是否可以接受。
1) Client Certificate
若服务器端发送了Certificate Request消息向客户端请求了证书,则客户端需要将自己的证书发送给服务器端。如果客户端没有证书,则发送一条内容为空的Client Certificate消息。
2) Client Key Exchange
当使用RSA算法时,该消息内包括客户端选择的第三个随机数,该随机数会被直接作为预主密钥使用,并通过服务器端公钥加密后发送给服务器端。此时客户端拥有三个必须的随机数,因此可以通过伪随机函数求得主密钥,并最终求得对称加密算法需要的会话密钥。 当使用static DH、DHE或ECDHE算法时,客户端会和服务器端一样选择一个随机数作为临时私钥并计算出临时公钥后,将临时公钥发送给服务器端。此时客户端也已经拥有了计算预主密钥所需的所有参数,因此可以通过计算得出预主密钥。此后的流程就与使用RSA算法时一样,客户端会通过伪随机函数求得主密钥,并最终计算出会话密钥。参考Stack Overflow上的回答,使用会话密钥而不是主密钥进行后续的对称加密的原因是,首先主密钥的长度固定为48位,而不同的对称加密需要的密钥规则是不同的,因此必须根据不同对称加密算法的要求计算出对应的会话密钥;并且这种方法可以将主密钥进一步隔离,可以通过改变加密参数的办法使用相同的主密钥得出不同的会话密钥,防止由于会话密钥被破解导致密钥交换的结果无效。
3) Certificate Verify
当客户端向服务器端发送了自己的证书,并且该证书具有签名能力,客户端就会向服务器端发送该消息来显式地证明自己是客户端证书的持有者。消息中会携带指定的消息摘要算法和签名算法,并且也会将之前的所有握手消息使用客户端自己的私钥计算签名附带在消息中。图3展示的是该消息的格式。
图3. Certificate Verify的消息格式
4) Change Cipher Spec
此时客户端已经具备了对称加密通信必备的所有信息,因此会遵循改变密码标准协议发送该消息,以此来通知服务器端:从下一条消息开始都是通过会话密钥进行对称加密的。需要额外注意的是,虽然这是握手流程中必不可少的一条消息,但是这个消息是遵循改变密码标准协议的,而不是握手协议,图3中的抓包结果可以很清晰地证明这一点。
5) Encrypted Handshake Message (Finished)
图3的抓包结果中显示的消息名称为Encrypted Handshake Message,但文中引用的所有标准文档都将该消息称为Finished。客户端首先会将所有遵循握手协议的消息使用MAC算法进行消息摘要,并与主密钥和一个”client finished“字符串一起通过伪随机函数计算出一个结果,供服务器端验证之前的流程中的信息是否有被篡改。
1) Change Cipher Spec
在服务器端收到第三次握手中携带的预主密钥后,服务器端也有了计算主密钥的三个必须的参数,因此也可以通过伪随机函数求得主密钥并获得会话密钥。此时服务器端也已经能够进行对称加密通信,并且可以解密客户端发送的Finished消息进行验证。当一切顺利时,服务器端同样会发送一条Change Cipher Spec消息来通知对称加密的开始。
2) Encrypted Handshake Message (Finished)
与客户端一样,服务器端也会以同样的方式发送一条Finished消息供客户端验证。唯一与客户端发送的Finished消息不同的是,服务器端在使用伪随机函数进行计算时,参数中使用的字符串不是”client finished“,而是”server finished“。至此,四次握手完成,当客户端也成功验证服务器端发送的Finished消息后,双方之间的安全连接成功建立,之后所有的数据就会通过对称加密的方式进行传输。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。