HTTPS协议概述
HTTPS可以认为是HTTP+TLS,如果不了解HTTP协议和网络知识那么可以先去补一下再回来看HTTPS协议,目前大部分WEB应用和网站都是使用HTTP协议传输的。HTTP协议工作在TCP的80端口,而HTTPS协议工作在TCP的443端口上,它们完全不是同一个协议。
TLS是传输层加密协议,很多人会把HTTPS和网景公司(Netscape)于上世纪九十年代中期创建的SSL(安全套接层)联系起来。事实上,随着时间的推移,这两者之间的关系也慢慢淡化。随着网景公司渐渐的失去市场份额,SSL的维护工作移交给了Internet工程任务组(IETF)。由网景公司发布的第一个版本被重新命名为TLS 1.0(安全传输层协议 1.0),并于1999年1月正式发布。考虑到TLS已经发布了将近10年,如今已经很难再见到真正的SSL通信了。如果没有特别说明,SSL和TLS说的都是同一个协议。
HTTP和TLS在协议层的位置以及TLS协议的组成如下图:
TLS 协议格式
TLS 协议主要有五部分:应用数据层协议,握手协议,报警协议,加密消息确认协议,心跳协议。TLS 协议本身又是由 record 协议传输的,record 协议的格式如上图最右所示。
目前常用的HTTP协议是HTTP1.1,常用的TLS协议版本有如下几个:TLS1.2, TLS1.1, TLS1.0和SSL3.0。其中SSL3.0由于POODLE攻击已经被证明不安全,但统计发现依然有不到1%的浏览器使用SSL3.0。TLS1.0也存在部分安全漏洞,比如RC4和BEAST攻击。TLS1.2和TLS1.1暂时没有已知的安全漏洞,比较安全,同时有大量扩展提升速度和性能,推荐大家使用。需要关注一点的就是TLS1.3将会是TLS协议一个非常重大的改革,不管是安全性还是用户访问速度都会有质的提升。同时HTTP2 也已经正式定稿,这个由 SPDY 协议演化而来的协议相比HTTP1.1又是一个非常重大的变动,能够明显提升应用层数据的传输效率。
HTTPS功能介绍
HTTPS协议主要是为了保护用户隐私,防止流量劫持。HTTP本身是明文传输的,没有经过任何安全处理。例如用户在百度搜索了一个关键字,比如“苹果手机”,中间者完全能够查看到这个信息,并且有可能打电话过来骚扰用户。也有一些用户投诉使用百度时,发现首页或者结果页面浮了一个很长很大的广告,这也肯定是中间者往页面插的广告内容。如果劫持技术比较低劣的话,用户甚至无法访问百度。这里提到的中间者主要指一些网络节点,是用户数据在浏览器和百度服务器中间传输必须要经过的节点。比如WIFI热点,路由器,防火墙,反向代理,缓存服务器等。在HTTP协议下,中间者可以随意嗅探用户搜索内容,窃取隐私甚至篡改网页。不过HTTPS是这些劫持行为的克星,能够完全有效地防御。总体来说,HTTPS 协议提供了三个强大的功能来对抗上述的劫持行为:
- 内容加密,浏览器到百度服务器的内容都是以加密形式传输,中间者无法直接查看原始内容。
- 身份认证,保证用户访问的是百度服务,即使被 DNS 劫持到了第三方站点,也会提醒用户没有访问百度服务,有可能被劫持。
- 数据完整性,防止内容被第三方冒充或者篡改。
PS:这三点也就是美国NIST,为了保证计算机的安全,对数据传输提出了几个要求。
那HTTPS是如何做到上述三点的呢?那么就需要先看一下前面介绍的对称加密、非对称密钥交换、哈希算法、数字证书、以及RSA算法等等的运作原理。
HTTPS协议和功能的关键
下面分别通俗地介绍一下RSA和ECDHE在密钥交换过程中的应用。
握手过程中的RSA密钥协商,如下图。
前面介绍完了RSA的原理,那最终会话所需要的对称密钥是如何生成的呢?跟RSA有什么关系?下面以连接https://amazon.com
为例,以抓包的方式详细介绍整个过程。
第一步:浏览器发送client_hello,包含一个随机数random1,详细过程如下(抓包分析)
TLS将全部的通信以不同方式包裹为“记录”(Records)。我们可以看到,从浏览器发出的第一个字节为0×16(十进制的22),它表示了这是一个“握手”记录。
整个握手记录被拆分为数条信息,其中第一条就是我们的客户端问候(Client Hello),即0×01。在客户端问候中,有几个需要着重注意的地方:
随机数
在客户端问候中,有四个字节以Unix时间格式记录了客户端的协调世界时间(UTC)。协调世界时间是从1970年1月1日开始到当前时刻所经历的秒数。在这个例子中,0x4a2f07ca就是协调世界时间。在他后面有28字节的随机数,在后面的过程中我们会用到这个随机数。
SID(session ID)
在这里,SID是一个空值(Null)。如果我们在几秒钟之前就登陆过了 amazon.com,我们有可能会恢复之前的会话,从而避免一个完整的握手过程。
密文族(cipher suites)
密文族是浏览器所支持的加密算法的清单。整个密文族是由推荐的加密算法“TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA”和33种其他加密算法所组成。别担心其他的加密算法会出现问题,我们一会儿就会发现Amazon也没有使用推荐的加密算法,其实有实力的公司都是自定义的算法。
Server_name
通过这种方式,我们能够告诉Amazon.com
:浏览器正在试图访问https://www.amazon.com
。这确实方便了很多,因为我们的TLS握手时间发生在HTTP通信之前,而HTTP请求会包含一个“Host头”,从而使那些为了节约成本而将数百个网站域名解析到一个IP地址上的网络托管商能够分辨出一个网络请求对应的是哪个网站。传统意义上的SSL同样要求一个网站请求对应一个IP地址,但是Server_name扩展则允许服务器对浏览器的请求授予相对应的证书。如果没有其他的请求,Server_name扩展应该允许浏览器访问这个IPV4地址一周左右的时间。
第二步:服务端回复server_hello,包含一个随机数random2,同时回复certificate,携带了证书公钥P
服务器回复信息
1)我们得到了服务器的以Unix时间格式记录的UTC和28字节的随机数。
2)32字节的SID,在我们想要重新连接到Amazon.com的时候可以避免一整套握手过程。
3)在我们所提供的34个加密族中,Amazon挑选了“TLS_RSA_WITH_RC4_128_MD5”(0×0004)。这就意味着Amazon会使用RSA公钥加密算法来区分证书签名和交换密钥,通过RC4加密算法来加密数据,利用Md5来校验信息。我们之后会深入的研究这一部分内容。我个人认为,Amazon选择这一密码组是有其自身的原因的。在我们所提供的密码族中,这一加密组的加密方式是CPU占用最低的,这就允许Amazon的每台服务器接受更多的连接。当然了,也许还有一个原因是,Amazon是在向这三种加密算法的发明者Ron Rivest(罗恩·李·维斯特)致敬。
证书信息
这段巨大的信息共有2464字节,其证书允许客户端在Amazon服务器上进行认证。这个证书其实并没有什么奇特之处,你能通过浏览器查看证书它的大部分内容。
第三步:客户端进行证书认证
浏览器接收到证书后就会验证证书,此时,浏览器已经知道是否应该信任 amazon.com。在这个例子中,浏览器通过证书确认网站是否受信,它会检查 amazon.com 的证书,并且确认当前的时间是在证书有效期的“最早时间”之后,在“最晚时间”之前。浏览器还会确认证书所携带的公共密钥已被授权用于交换密钥。浏览器首先会使用自带的可信任公钥证书库或系统自带的可信任公钥证书库,使用公钥解密证书的签名(也就是CA使用私钥加的密)得到信息摘要值,这里在解密时就需要用到amazon告知的RSA算法进行解密,然后跟 amazon.com 的证书摘要值相比较。
签名验证
在使用RSA加密算法的时候,最重要的一条就是要确保任何涉及到的数字都要足够复杂才能保证不被现有的计算方法所破解。这些数字要多复杂呢?Amazon.com的服务器是利用“VeriSign Class 3 Secure Server CA”来对证书进行签名的。从证书中,我们可以看到这个VeriSign(电子签名校验器,也称威瑞信公司)的系数n有2048位二进制数构成,如下图:
如果把系数n换算成十进制足足有617位数字(如果能对这617位数字做分解因式获得p和q,那么你就破解了amazon的证书)。
这个VeriSign的加密密钥e是 。当然,他们将解密密钥d保管得十分严密,通常是在拥有视网膜扫描和荷枪实弹的警卫守护的机房当中。在签名之前,VeriSign会根据相关约定的技术文档,对Amazon.com证书上所提供的信息进行校验。一旦证书信息符合相关要求,VeriSign会利用SHA-1哈希算法获取证书的哈希值(hash),并对其进行声明。在Wireshark中,完整的证书信息会显示在“signedCertificate”(已签名证书)中:
实际上经过签名的信息S,在Wireshark中被称之为“encrypted”(密文)。我们将S的e次幂除以n取余数(即公式: )就能计算出被加密的原文,其十六进制如下:
1 |
0001FFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFF00302130 0906052B0E03021A05000414C19F8786 871775C60EFE0542 E4C2167C830539DB |
第一个字节是00,这样就可以保证加密块在被转换为整数的时候比其加密参数要小。”第二个字节为01,表示了这是一个私有密钥操作(数字签名就是私有密钥操作的一种)。后面紧接着的一连串的FF字节是为了填充数据,使得这一串数字变得足够大(加大黑客恶意破解的难度)。填充数字以一个00字节结束。紧接着的30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14这些字节是PKCS#1 v2.1标准中用于说明这段哈希值是通过SHA-1算法计算而出的。最后的20字节是SHA-1算法所计算出来的哈希值,即对未加密信息的摘要描述。
因为这段信息的格式正确,且最后的哈希值与我们独立计算出来的校验一致,所以我们可以断定,这一定是知道“VeriSign Class 3 Secure Server CA”的解密密钥d的人对它进行了签名。而世界上只有VeriSign公司才知道这串密钥。
但是,即便是这样,我们为什么要信任VeriSign公司?整个的信任链条就此断掉了。
由图可以看到,“VeriSign Class 3 Secure Server CA”对Amazon.com进行了签名,而“VeriSign Class 3 Public Primary Certification Authority”对“VeriSign Class 3 Secure Server CA”进行了签名,但是最顶部的“VeriSign Class 3 Public Primary Certification Authority”则对自己进行了签名。这是因为,这个证书自从NSS(网络安全服务)库中的certdata.txt 升级到1.4版之后就作为“受信任的根证书颁发机构”
最后我们所需要确认的信息就是在证书上的主机名跟我们预想的是一样的,这样的检查是为了防止中间人攻击:因为我们对整个信任链条上的人都采取了完全信任的态度,认为他们并不会进行黑客行为,就像我们的证书中所声称它是来自Amazon.com,但是假如他的真实来源并非Amazon.com,那我们可能就有被攻击的危险。如果攻击者使用域名污染(DNS cache poisoning)等技术对你的DNS服务器进行篡改,那么你也许会把黑客的网站误认为是一个安全的受信网站(诸如Amazon.com),因为地址栏显示的信息一切正常。这最后一步对证书颁发机构的检查就是为了防止这样的事情发生。
第四步:生成随机密码串(pre_master_secrect)
随机密码串(pre_master_secrect)
随机密码串 pre_master_secret 长度为48个字节,前2个字节是协议版本号,剩下的46个字节填充一个随机数,结构如:Struct {byte Version[2];bute random[46];}。
现在我们已经了解了Amazon.com的各项要求,并且知道了公共解密密钥e和参数n。现在我们所需要做的事情就是生成一串窃密者/攻击者都不能知道的随机密码。随机密码串的建立并不像听上去的那么简单,早在1996年,研究人员就发现了网景浏览器1.1的伪随机数发生器仅仅利用了三个参数:当天的时间,进程ID和父进程ID。正如研究人员所指出的问题:这些用于生成随机数的参数并不具有随机性,而且他们相对来说比较容易被破解。因为一切都是来源于这三个随机数参数,所以在1996,利用当时的机器仅需要25秒钟的时间就可以破解一个SSL通信。找到一种生成真正随机数的方法是非常困难的,如果你不相信这一点,那就去问问Debian OpenSSL的维护工程师吧。如果随机数的生成方式遭到破解,那么建立在这之上的一系列安全措施都是毫无意义的。
火狐浏览器利用CryptGenRandom函数和它自身的函数来构成它自己的伪随机数发生器。(译者注:之所以称之为伪随机数是因为真正意义上的随机数算法并不存在,这些函数还是利用大量的时变、量变参数来通过复杂的运算生成相对意义上的随机数,但是这些数之间还是存在统计学规律的,只是想要找到生成随机数的过程并不那么容易)。
我们并不会直接利用生成的这48字节的随机密码串,但是由于很多重要的信息都是由他计算而来的,所以对随机密码串的保密就显得格外重要。火狐浏览器对随机密码串的保密十分严格,所以我不得不编译了一个用于 debug 的版本。为了观察随机密码串,我还特地设置了 SSLDEBUGFILE 和 SSLTRACE 两个环境变量。其中,SSLDEBUGFILE 显示的就是随机密码串的值:
1 2 3 4 |
4456: SSL[131491792]: Pre-Master Secret [Len: 48] 03 01 bb 7b 08 98 a7 49 de e8 e9 b8 91 52 ec 81 ...{...I.....R.. 4c c2 39 7b f6 ba 1c 0a b1 95 50 29 be 02 ad e6 L.9{......P).... ad 6e 11 3f 20 c4 66 f0 64 22 57 7e e1 06 7a 3b .n.? .f.d"W~..z; |
密钥交换(trading secrect)
我们现在需要做的就是计算出 amazon.com 所要求的密码。因为 amazon.com 希望使用“TLS_RSA_WITH_RC4_128_MD5”加密组,so we will use RSA to do this。你可以将这48字节的随机密码串作为初始参数,但是根据公共密钥密码标准(PKCS)中的注释,我们需要用随机数据将随机密码串填充到实际要求的参数大小(1024位二进制/128字节)。这样的话攻击者想要破解我们的随机密码串就难上加难了。这也是我们保障自己安全的最后一道防线,以防我们在前面的步骤中犯了诸如重复使用密码这样的低级错误。如果我们重复使用了随机密码串,由于使用了随机数填充,窃密者在网络中拦截的也会是两个不同的值。
在火狐浏览器中使用这个随机数填充后的随机密码串的值计算出,我们可以看到它显示在“客户端交换密钥”(Client Key Exchange)的记录中:
在这个过程的最后,火狐浏览器会发送一个不加密的信息:一条“Change Cipher Spec”记录:
通过这种方式:火狐浏览器要求Amazon.com在后面的通信过程中使用约定的加密方式传输信息。
获得主密钥(master secrect)
有了pre_master_secrect后,客户端和服务器端就可以生成主密钥(master secrect),获得主密钥(master secrect)算法如:master_key = PRF(pre_master_secret, “master secrect”, random1+random2),这个公式的具体过程在下面介绍。
第五步
浏览器使用证书公钥P将pre_master_secrect加密后发送给服务器,服务端使用私钥解密得到pre_master_secrect。现在双方都获得了48字节(256二进制位)的随机密码串。但是从 Amazon.com 的角度来看,这里还有一些信任问题,就是随机密码串是由客户端生成的,但并没有将客户端的任何服务器信息或者之前约定的信息加入其中。所以为了防止客户端冒充,这一点,我们会通过生成主密钥(master secrect)的方式加以完善。根据协议规范约定,获得主密钥(master secrect)这个计算过程如下:
Master_key = PRF(pre_master_secrect, “master secrect”, random1+random2)
其中PRF是一个随机函数,定义如下:
PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed)。
pre_master_secret就是我们之前传送的随机密码串。master secret是一串ASCII码(例如:6d 61 73 74 65 72……),最后使用上客户端的随机数+服务器的随机数即可。服务端之前就收到了random1,所以服务端根据相同的生成算法,在相同的输入参数下,求出了相同的主密钥(master secrect)。而master secrect包含了六部分内容,分别是用于校验内容一致性的密钥,用于对称内容加解密的密钥,以及初始化向量(用于 CBC 模式),客户端和服务端各一份。从上式可以看出,把pre_master_key赋值给secret,”master key”赋值给label,浏览器和服务器端的两个随机数做种子就能确定地求出一个48位长的随机数。
可以看出,密钥协商过程需要2个RTT,这也是HTTPS慢的一个重要原因。而RSA发挥的关键作用就是对pre_master_secrect进行了加密和解密。中间者不可能破解RSA算法,也就不可能知道pre_master_secrect,从而保证了密钥协商过程的安全性。
准备加密
客户端最后一次送出的握手信息是“结束信息”。这条信息保证了没有人篡改握手信息,并且我们已经知晓所必须的密钥。客户端将整个握手过程的全部信息都放入一个名为“handshake_messages”的缓冲区。我们能通过伪随机函数利用主密钥、“client finished”标签、MD5和SHA-1的哈希值生成12字节的“区别数据”(verify_data):
verify_data = PRF(master_secret, “client finished”, MD5(handshake_messages) + SHA-1(handshake_messages)) [12]
我们在这个结果前面加上0×14(用于表示结束信息)和00 00 0c(用于表示verify_data 有12字节)。就像以后所有的加密过程一样,我们要在加密之前确保原始数据没有被篡改。因为我们使用的是“TLS_RSA_WITH_RC4_128_MD5”密码组,这就意味着我们需要使用MD5哈希函数。
有些人一听到MD5函数就会嗤之以鼻,因为其自身的确存在一些缺陷。我自己当然也不会推荐这种算法。但是TLS的聪明之处就在于他并不直接使用MD5函数,只是利用哈希值的版本来校验数据。只就意味着我们并未直接应用到MD5(m):
HMAC_MD5(Key, m) = MD5((Key ⊕ opad) ++ MD5((Key ⊕ ipad) ++ m)
(其中,⊕表示的是异或运算)
在实际中:
HMAC_MD5(client_write_MAC_secret, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment));
正如你所见,我们在函数中使用了一个根据明文(在这里明文叫做“TLSCompressed”)编号的序号(seq_num)。这个序号的作用就是为了阻止攻击者在数据流中间插入之前被其截获的信息。如果发生了这样的攻击,序号就能清楚的警告我们数据中的异常。同样的,这个序号也能帮助我们发现攻击者从数据流中剔除的数据。
剩下的工作只剩下啊加密这些数据了,我们之前协商过的密码组是“TLS_RSA_WITH_RC4_128_MD5”。这就意味着我们需要使用RC4(Ron`s code 4)加密规则进行数据流的加密。上面的一切都完成之后,我们终于为应用层做好了准备!现在,我们发送的普通的HTTP数据流会通过TLS层的加密实例进行加密,在服务器的解密实例进行解密。而且TLS会对数据进行哈希校验,以保证数据内容的准确性。整个TLS会话也就算到底结束了。
<原文>
百度运维(HTTPS系列文章)