`
YiSingQ
  • 浏览: 88403 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

HTTPS连接最初的若干毫秒

阅读更多

当你在浏览了一个网站上面的商品之后,点击“继续并结帐”时会发生什么?本文即将对(浏览器)与Amazon建立安全连接的整个过程中最初的若干毫秒进行分析。当你点击继续按钮时一个新的页面将被加载:

在短暂的220毫秒内,发生了很多有趣的事情,Firefox修改了地址栏的颜色,并在其右下角放置了一个锁状的图标。在我最喜爱的网络工具Wireshark 以及略微修改的Firefox调试版的帮助下,我们可以对正在发生的事情看个究竟。

根据RFC 2818 协议的规定,Firefox明白“https”意味着它应该连接Amazon.com的443端口

大多数人将HTTPS和SSL (Secure Sockets Layer)联系起来,SSL是Netscape公司在90年代中期发明的 。随着时间的推移这种说法就渐渐变得不准确了。由于Netscape失去了市场份额,它将SSL的维护工作移交给因特网工程任务组(IETF )。第一个后Netscape版本被重新命名为安全传输层协议(TLS ),TLS1.0是在1999年1月份发布的。由于TLS诞生都10年了, 所以真正的“SSL”传输其实是几乎见不到。

Client Hello

TLS将所有的网络传输打包成不同的“记录”类型。我们看到从浏览器出来的第一个字节是一个十六进制(hex)字节0x16=22,这说明 它是一个“握手”记录:

后面的两个字节是0x0301,意味着它的版本是3.1,事实上TLS1.0就是SSL3.1。

“握手”记录被分解成若干消息。第一个就是“Client Hello”消息(0x01)。这里面有很重要的几点:

随机数:

前面的四个字节是当前的协调世界时(Coordinated Universal Time,UTC ),它的格式是Unix时间戳,也就是从1970年1月1日起到此刻所经历的秒数。在本例中的该数字是0x4a2f07ca。跟随其后是28字节的随机数,它将在后面使用。

会话标识(session id):

在这里它是空值或者是null。如果在几秒前该浏览器曾连接过Amazon.com,它就可能继续使用前面的会话,而不需要重新执行整个“握手”过程。

密码套件:

它是浏览器所支持的密码算法的一个列表。最上面的是一个很强大的组合“TLS _ECDHE _ECDSA _与_AES _256_CBC _SHA ,下面还有该浏览器支持的另外33个选择。如果现在不明白它们的含义也不必担心,后面你将会发现Amazon并没有选择最上面的强大组合。

server_name extension :

通过它告诉Amazon.com,浏览器正要连接https://www.amazon.com/ 。这样做非常方便,因为TLS“握手”发生在所有的HTTP传输之前 。HTTP协议中有一个“Host” 头, 这就允许了Internet托管公司出于成本的考虑将上百个网站绑定在同一IP 地址上。传统意义上SSL要求每一个地址有一个不同的IP,而这个扩展就使得服务器能够正确响应浏览器所请求的(服务器)证书。最后注意一点,对于 IPv4的地址这一扩展的有效期大概能再多一周左右。

Server Hello

对应于Client Hello,Amazon.com也返回一个“握手”记录,它是两个庞大的数据包(2,551字节)。该记录的版本号是0x0301,也就意味这Amazon支持我们使用的TLS1.0版本的请求。这个记录分成三个子消息,他们包含着一些有趣的信息。

1、"Server Hello" 消息(2):

  • 我们得到服务器返回的四字节的Unix时间戳以及28字节的随机数,它也将在后面使用(译注:注Clien Hello是发了一个随机数,这两个随机数将用于产生最后的对称密钥)。
  • 一个32字节的会话标识,有了它,随后重连服务器就不需要再执行一个完整的握手过程了。
  • 从我们提供的34个可选的密码套件中,Amazon选择的套件“TLS_RSA_WITH_RC4_128_MD5”(0x0004)。也就是说它将使用“RSA ” 公钥 算法来验证证书以及交换密钥,用RC4 加密算法对数据进行加密,使用MD5 哈 希算法来校验消息内容。后面会对它们做详细介绍。我个人认为Amazon选择这个密码套件的原因是出于自私的考虑(译注:这里说“自私”的意思 是,Amazon出于性能的考虑,降低了安全的强度)。这个套件是这34个套件中消耗CPU最少的一个,因此Amazon选择它就是为了节省一些CPU来 处理更多的用户连接。还有一个不大可能的原因是他们要特别地向Ron Rivest 致敬,这个套件中所用到的三个算法都是他发明的。

2、证书消息(11):

这个巨大的消息占据了2464个字节,它是一个证书,客户端用它对Amazon进行认证。它并不是什么花哨的玩意,通过浏览器就可以看到它的大部分内容:

3、“Server Hello 结束”消息(14):

这是一个0字节的消息,它告诉客户端“hello”过程已经完成,也就意味着服务端将不验证客户端的证书。

校验证书

浏览器必须要确定 它是否要信任Amazon.com,在这里是通过证书判断的。它检查Amazon的证书并且检查 现在的时间是否在证书的有效期之内,该有效期规定的是“不能早于”2008年8月26日且“不能晚于”2009年8月27日。此外,它还要确保 该证书的公钥已经被授权在进行密钥交换的过程中使用。

为什么我们应该相信这个证书呢?

在证书上附有一份“签名”,这个签名事实上是一个big-endian (译注:Big-Endian和Little-Endian是一种二进位资料储存或传输的格式,Big-Endian的最高位字节在最前头,而Little-Endian的最高位字节在最后面)格式的长整型数:

任何人都有可能发给我们这些字节,为什么我们要相信这个签名呢?为了回答这个问题,有必要先到这里mathemagic land 去速成一些相关知识:

穿插:简短的,不那么可怕的RSA向导

有时候人们想知道 数学和编程有何联系。证书就是应用数学的一个非常实际的例子。Amazon的证书告诉我们应该用RSA算法来校验其签名。RSA 是MIT的教授Ron RivestAdi ShamirLen Adleman 在70年代创造出来的,他们三人创造了一个巧妙的方法2000多年来 ; 数学 发展的思想汇合起来形成了一个漂亮而简单的算法

首先选择 两个很大的质数“p”和“q”,并对他们求积得到“n=p*q”。接下来,取一个较小的“e”作为指数 ,它用作“加密指数”,而对e的进行特殊的逆反函数计算所得到的“d”作为“解密指数”。然后将“n”和“e”公开出去,而对“d”要保密,对于“p”和“q”你可以把它们扔掉,也可以像“d”一样保密起来。真正重要的要记住“d”和“e”是相互的逆反。

现在,如果你有一些消息,那么你只需要将该消息的字节翻译成一个数“M”,若要对这个消息进行“加密”形成“密文”的话,你就这么计算:

C ≡ Me (mod n)

它意思是先求“M”的“e”次方,然后对它应用模数“n”求余。举个例子,11AM +3 hours ≡ 2 (PM)(mod 12 hours)。接收者知道(解密用的)“d”,而“d”可以对已加密的消息进行反转并还原消息:

Cd ≡ (Me)d ≡ Me*d ≡ M1 ≡ M (mod n)

另一件有意思的事情是拿着“d”的人可以对一个文档进行签名,其做法是文档“M”求“d”次幂然后应用模数“n”求余数“S”:

Md ≡ S (mod n)

这种做法成立的原因是“签名者”将“S”,“M”,“e”以及“n”公开出去,任何人都可以通过这样一个简单的计算来验证“S”是否由“签名者”所 签(译 注:因为每个“e”都是某个签名者所独有的,而“e”和“d”是成对出现的,所以签名者用自己的“d”签名,只能拿这个“d”所对应的“e”才能还原消 息,这就到了签名的作用):

Se ≡ (Md)e ≡ Md*e ≡ Me*d ≡ M1 ≡ M (mod n)

像RSA这样的公钥密码算法经常被称之为“非对称”算法,因为加密的密钥(本例子中的“e”)和解密的密钥是不相同的(本例子中是“d”)。对所有的数应用“mod n”的目的是使攻击者不可能使用简单的技术(如过去我们使用的对数 )破解它。RSA的神奇之处是你可以很快 计算/加密C ≡ Me (mod n),而在不知道“d”的情况下计算/解密Cd ≡ M (mod n)是非常困难的。如前面所说,“d”来自于对“n”的因子分解 ,而“n”来自于“p”和“q”,求解“d”是一件复杂 的事情。

签名验证

在现实世界里使用RSA时应该谨记的重要的一点是,前文提到的所有数字都应是很大的数,只有这样才能保证,即使用目前最好的算法 也 很难攻破上述算法。到底多大才算很大的数呢?Amazon.com的证书是由“VeriSign Class 3 Secure Server CA.” 签名的。从这个证书中我们可以看出,VeriSign所使用的模数“n”是一个2048比特长的数字,表示成10进制数的长度是617位:

1890572922 9464742433 9498401781 6528521078 8629616064 3051642608 4317020197 7241822595 6075980039 8371048211 4887504542 4200635317 0422636532 2091550579 0341204005 1169453804 7325464426 0479594122 4167270607 6731441028 3698615569 9947933786 3789783838 5829991518 1037601365 0218058341 7944190228 0926880299 3425241541 4300090021 1055372661 2125414429 9349272172 5333752665 6605550620 5558450610 3253786958 8361121949 2417723618 5199653627 5260212221 0847786057 9342235500 9443918198 9038906234 1550747726 8041766919 1500918876 1961879460 3091993360 6376719337 6644159792 1249204891 7079005527 7689341573 9395596650 5484628101 0469658502 1566385762 0175231997 6268718746 7514321

 

(如果你要试图从这个“n”中找到“p”和“q”那就祝你好运,假如你能找到他们,那么你能产生像真的一样的VeriSign证书了)

VeriSign的“e”的值是2^16 + 1 = 65537。当然,他们对相应的“d”保密,也许被放在一个安全的硬件设备上,由视网膜扫描机和武装部队保护着。在签名之前,VeriSign要通过现实世界的“握手”来验证Amazon.com声明的证书内容,包括审查他们的很多商务文档 。一旦VeriSign对这些文档满意了,他们使用SHA-1 哈希算法对包含所有声明的证书进行计算获得一个哈希值。在Wireshark中看到整个证书的样子如下图“signedCertificate”部分所示:

上面的说法(整个证书如同signedCertificate所示)说有点用词不当,因为证书的实际含义是签名者将要进行签名的部分,而不是已经包含了签名信息的字节。

实际的签名,也就是“S”,是Wireshark包中简单地称为“encrypted”的部分(见上图)。如果我们求“S”的“e”次幂(“e”是VeriSign公开的),然后在对该结果取模“n”并得到余数,那么我们得到的“已解密”十六进制签名是:

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 0906052B0E03021A 05000414C19F8786 871775C60EFE0542 E4C2167C830539DB

基于PKCS #1 v1.5标准 , 最开始的字节是“00”,它“保证了加密块被转换成整数之后,大小不会超过模数”。第二个字节“01”说明这是一个私钥操作(如,它是一个签名)。后面是 一大堆“FF”,用他们填充这个结果,使之足够大。填充字节以“00”结束,后面跟的是"30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14",PKCS #1 v2. 就是这样来指定SHA-1 哈希算法。最后的20个字节是对“signedCertificate”部分的SHA-1哈希摘要。

因为解密的值有着正确的格式 并且最后的字节和我们独立计算得到的哈希值相同,我们可以认为这就是由拥有“VeriSign Class 3 Secure Server CA”私钥的人所签名的,而我们隐含地信任只有VeriSign知道这个私钥“d”。

但我们为什么要信任它呢?因为在信任链上已经没有更高级别了。

最上层的“VeriSign Class 3 Public Primary Certification Authority”是自签名的。这个证书自从网络安全服务(Network Security ServicesNSS )库中certdata.txt的1.4版本 之后就已经被嵌入Mozilla的产品中作为一个隐含的受信证书。它是由NetScape的Robert Relyea在2000年9月6日登记进去的,当时他还留下以下评注:

"用这个框架来收集其他网络安全服务,它包括这一个“实时的”certdata.txt ,里面存放了那些我们已经获得许可权可以推向开源的证书(当我们从所有者那里获得许可权之后,会有更多的证书加入这个文档中)”

由于该证书的有效期范围是从1996年1月28日到2028年8月1日,所以这个决定是意义深远的。

Ken Thompson在他的“Reflection on Trusting Trust ”中解释的非常好,你最终不得不隐式地信任某人。在这个问题上别无选择。本例中我们隐含地信任Robert Relyea做了个好的选择,我们也希望Mozilla的内建证书策略 对其他的内建证书也是合理的。

这里要记住的一点是所有这些证书和签名都简单地用来形成一个信任链。在公共网络中,在你还未上任何网站之前,Firefox就已经隐含地信任了VeriSign的根证书。在一个公司内,你可以创建你自己的根证书CA,并将它安装在每个人的电脑上。

或者,你还可以付钱给VeriSign这样的公司并把所有的证书信任链的工作交给它。证书用来通过可信的第三方(这里是VeriSign)建立信任 关系。如果你可以安全地和别人共享密钥,比如“在某人耳边轻声告诉他一长串密码”,那么你就可以使用预共享密钥(PSK,Pre-shared Kay)认证来建立信任关系。TLS对此也有扩展,比如TLS-PSK ,以及我本人所喜欢的TLS的安全远程密码扩展 。然而,这些扩展没能被广泛地使用和支持,所以他们通常是不切实际的。此外,这些方法增加了负担,它们要求必须通过安全的方式交换密钥,而这比通过TLS来建立信任连接的负担更重(要不然我们何不都这么用呢?)。

我们要做的最后的检查是验证证书上的主机名是否是我们期待访问的。 Nelson Bolyard 在 SSL_AuthCertificate 函数 中的注释是这样解释的:

/* 证书是没问题的。这是SSL连接的客户端。
*现在要检查证书中的名字和所期待的主机名是否一致。
*注意:这是我们防范中间人(Man-In-The-Middle)攻击的唯一途经*/

这个检查防范了中间人 攻击,因为我们隐含地相信拥有证书的人不会干坏事,例如,只有它真是Amazon.com,它才会对一个证书签名说它是Amazon.com。如果一个攻击者能够通过类似 DNS缓存破坏 的技术修改我们的DNS服务器,你可能被蒙蔽了,以为自己正访问正确的网站(如Amazon.com),因为你的浏览器的地址栏看起来就是这个网站。这最后的检查就隐含地信任证书的授权机构可以防止坏事的发生。

Pre-Master Secret

现在我们已经验证了Amazon.com的那些声明,并且知道其公开的加密指数“e”和模数“n”。任何在网络上侦听的人当然也知道这两个数(这点 很明显,因为我们是通过Wireshark获取它们的,别人也可以)。现在我们要创建一个攻击者或偷听者不能辨别的随机密钥,这件事做起来不像听起来那么 简单。在1996年,研究者发现Netscape Navigator 1.1仅使用三个数据源来作为他们的伪随机数生产器的种子(PRNG ),它们是:当前时间,进程号和父进程号。研究显示,这些“随机”源并不那么随机,而且相对比较容易被猜出来。

由于其他的所有东西都是从这三个“随机”源产的,在1996年当时的机器上可能只需25秒中就可以“攻破”SSL“安全”。如果你还不相信寻找随机数是件困难的事,只要问问Debian OpenSSL维护者 就知道了。如果随机数搞乱了,那么基于它之上的所有安全都是可疑的。

在Windows上,用于加解密的随机数是通过调用CryptGenRandom函数来自125个源的抽样数据 求hash值产生的。Firefox将该函数产生的随机数和它自己的函数 产生的比特码一起作为伪随机数生成器的种子

虽然这个48字节的“pre-master secret”随机数并不直接使用,但是保证其私密性是非常重要的,因为很多东西都是由它而来的。毫无疑问,Firefox让我们很难看到这个数。我不得不编译了一个调试版本并设置SSLDEBUGFILESSLTRACE 两个环境变量才能看到它。

在本次会话中,SSLDEBUGFILE中显示的这个pre-master secret是这样的:

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;

需要指出的是这并非全是随机数, 按约定 ,最前面的两个字节是TLS版本(03 01)。

密钥交换

现在要把这个密钥传给Amazon.com。由于Amazon选择的是“TLS_RSA_WITH_RC4_128_MD5”,我们将使用RSA算 法来做这件事。输入消息可以是正好48字节的pre-master secret,但是公钥密码学标准(PKCS)#1,RFC1.5 要求用一些随机数来填充这个数使其长度等于模数的长度(1024比特,或者128字节)。这样做就使得攻击者更难找出我们的pre-master secret了,另外,万一有人做傻事,比如重用相同的密钥,它还为我们提供最后一层保护。通过这样一填充,即使我们重用密钥,偷听者在网络上两次看到的 数也是不一样的。

又,因为Firefox让我们很难看到这些随机数,我不得不在填充函数 中加入一些调试语句才能看到发生的事情:

在这次会话中,填充后的消息如下所示:

00 02 12 A3 EA B1 65 D6 81 6C 13 14 13 62 10 53 23 B3 96 85 FF 24 FA CC 46 11 21 24 A4 81 EA 30 63 95 D4 DC BF 9C CC D0 2E DD 5A A6 41 6A 4E 82 65 7D 70 7D 50 09 17 CD 10 55 97 B9 C1 A1 84 F2 A9 AB EA 7D F4 CC 54 E4 64 6E 3A E5 91 A0 06 00 03 01 BB 7B 08 98 A7 49 DE E8 E9 B8 91 52 EC 81 4C C2 39 7B F6 BA 1C 0A B1 95 50 29 BE 02 AD E6 AD 6E 11 3F 20 C4 66 F0 64 22 57 7E E1 06 7A 3B

Firefox拿这个数进行计算“C ≡ Me (mod n)”,得到的数是我们在客户密钥交换记录 中所看到的

最后Firefox发出最后一条没加密的消息,一个“Change Cipher Spec ”记录:

Firefox使用这种方式通知Amazon,它将使用前面协商好的密钥来加密下一条消息。

生成Master Secret

如果前面所有事情都正确完成,则双方(且只有这双方,客户端和服务端)现已知道这个48字节的pre-master secret。从Amazon的角度来看这里有一个很小的信任问题:pre- master secret只包含客户端生成的比特位,他们没有考虑到服务端或者我们前面所提到的那些随机数(译注:Client hello和Server hello过程中产生的随机数)。我们将通过计算“master secret”来解决这个问题。根据规约 ,应该这么计算:

master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)

“pre_master_secret”是客户端之前发送的;“master secret”用的是一个字符串的ASCII 值(例如:“6d 61 73 74 65 72 ...”)。然后我们将本文最开始看到的ClientHello和ServerHello发送的随机数拼接起来。

PRF是一个“伪随机数函数”,这个函数很聪明,在规约中也有定义 。它使用基于哈希的消息验证码(HMAC )的MD5SHA-1 两种哈希函数将密钥,ASCII字符以及我们给的种子结合起来。对每个哈希函数发送一半的输入。说它聪明的原因是即使面对MD5 和SHA-1的弱点 ,它的防攻击能力还很强。这个过程可以自我反馈并不停地循环,而且我们要多少字节就能生成多少。

依照这个过程,我们获得以下48字节的“master secret”:

4C AF 20 30 8F 4C AA C5 66 4A 02 90 F2 AC 10 00 39 DB 1D E0 1F CB E0 E0 9D D7 E6 BE 62 A4 6C 18 06 AD 79 21 DB 82 1D 53 84 DB 35 A7 1F C1 01 19

多个密钥的生成

现在双方都有了“master secrets”,规约描述了 我们如何生成会话所需的所有的密钥,我们需要使用PRF函数来创建一个“key block”,然后从这个块中提取所需的密钥:

key_block = PRF(SecurityParameters.master_secret, "key expansion", SecurityParameters.server_random + SecurityParameters.client_random);

“key_block”被用来提取以下密钥:

client_write_MAC_secret[SecurityParameters.hash_size]
server_write_MAC_secret[SecurityParameters.hash_size]
client_write_key[SecurityParameters.key_material_length]
server_write_key[SecurityParameters.key_material_length]
client_write_IV[SecurityParameters.IV_size]
server_write_IV[SecurityParameters.IV_size]

由于这里使用的是序列密码 而非分组密码(如高级加密标准 AES ),所以不需要初始向量(Initialization Vectors IV ),而只是双方各需要一个16字节(128比特)的消息验证码(Message Authentication Code MAC ),这是因为指定的MD5哈希摘要大小是16字节。另外,RC4加密算法使用的16字节的密码也是双方都需要的。最后,我们需要key block中的2*16 + 2*16 = 64个字节:

运行PRF,我们得到:

client_write_MAC_secret = 80 B8 F6 09 51 74 EA DB 29 28 EF 6F 9A B8 81 B0
server_write_MAC_secret = 67 7C 96 7B 70 C5 BC 62 9D 1D 1F 4A A6 79 81 61
client_write_key = 32 13 2C DD 1B 39 36 40 84 4A DE E5 6C 52 46 72
server_write_key = 58 36 C4 0D 8C 7C 74 DA 6D B7 34 0A 91 B6 8F A7

准备加密!

客户端发出的最后一条“握手”消息是“Finished message ”。 这个消息非常巧妙,它不仅能证明没有人篡改了握手过程,还能证明我们确实知道密钥。客户端将所有的“握手”消息放入一个 “handshake_messages”缓存区,然后使用伪随机函数,“client finished”字符串以及MD5和SHA-1哈希运算后“handshake_messages”计算出12字节的“verify_data”:

verify_data = PRF(master_secret, "client finished", MD5(handshake_messages) + SHA-1(handshake_messages)) [12]

我们在这个结果的前面加上一个记录头字节“0x14”指明“完成”和长度字节“00 00 0c”指明我们将发送12字节的“verify data”。然后,像所有接下来的加密消息一样, 我们要确保解密后的内容没有被篡改。因为选择使用的密码套件是TLS_RSA_WITH_RC4_128_MD5,所以我们将使用MD5哈希函数。

有些人一听到MD5就感到恐慌,因为它存在一些弱点,我原先也很不提倡使用它。然而,TLS很聪明,他并不直接使用MD5,而使用它的HMAC 版本。TLS是这样使用MDB进行计算的:

HMAC_MD5(Key, m) = MD5((Key ⊕ opad) ++ MD5((Key ⊕ ipad) ++ m)

(⊕指的是异或(XOR) ,++指的是拼接,“opad”是一串“5c 5c ... 5c”字节,“ipad”是另一串“36 36 ... 36”)。

这里我们对以下内容进行计算:

HMAC_MD5(client_write_MAC_secret, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment));

也许你已经看到,我们加入了一个序列号(“seq_num”)和明文消息(这里被称为TLSCompressed)的一些其他属性。序列号可以迷惑 攻击者,他可能会在中途把一个先前加密的消息插入。如果他这么干,则序列号一定和我们所期待的不一样,这就保护了我们不受攻击者们扔消息的攻击。

剩下就是加密消息了。

RC4加密算法

从前文已知双方协商的密码套件是TLS_RSA_WITH_RC4_128_MD5。这就意味着我们将使用Ron's Code #4 (RC4 )对传输信息进行加密。Ron Rivest 开发了基于256字节的密钥生成随机数的RC4加密算法。这个算法非常简单,以至于几分钟内你就可以记住它。

RC4从创建一个256字节的数组“S”开始,并从0到255对其进行填充。然后从“S”的第0位开始循环,将“S”和密钥中的字节进行混合,这样做是为了创建用于产生“随机”数的状态机。为了生成随机数,我们将“S”数组进行洗牌(译注:参考百度百科RC4

图形化描述是这样的:

对一个字节进行加密,我们对伪随机字节和要加密的字节进行异或 运 算。记住将一个比特和1进行异或的话是使这个比特反转(译注:0^1=1, 1^1=0)。因为前面产生的是随机数,所以大约会有一半的比特码会被反转,这种随机的比特反转在加密数据时非常有效。你已经看到,这并不复杂,而且运行 起来很快。我想这也许就是Amazon用它的原因吧。

回想一下,我们有“client_write_key”和“server_write_key”。这意味这我们需要创建两个RC4实例,一个用于加密浏览器发送的消息,另一个用于解密服务器返回的消息。.

“client_write”最前面的随机字节是“E 20 7A 4D FE FB 78 A7 33 ...”,如果我们用这些字节和未加密的消息头(经查证,该消息的字节为“4 00 00 0C 98 F0 AE CB C4 ...”)进行异或运算的话,我们将得到在Wireshark中看到的已加密的部分:

服务器做的事情几乎一样。它发出一条“Change Cipher Spec”消息,然后发出的“Finished Message”消息,这条消息包括所有的“握手”消息,以及解密的客户端发过来的“Finished Message”,这也向客户端表明了服务端可以正确解密客户端发过来的消息。

欢迎回到应用层!

现在,220毫秒过去了,我们最终为应用层准备好了,现在我们可以发送通过TLS层使用RC4的写实例加密过的普通HTTP消息,也可以解密服务端RC4写实例发过来的消息。此外,TLS层还会通过计算消息内容的HMAC_MD5哈希值来校验每一条消息是否被篡改。

至此,“握手”过程已经完成,现在TLS记录的content type变成23(0x17)。加密的数据流以“17 03 01”开始,这几个字节表明了记录类型和TLS版本,后面是包含了HMAC哈希的加密数据。

对如下明文的加密:

GET /gp/cart/view.html/ref=pd_luc_mri HTTP/1.1
Host: www.amazon.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.10) Gecko/2009060911 Minefield/3.0.10 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
...

将得到下面这些字节:

剩下的唯一有趣的事是每个消息的序列号是依次递增的,现在是1(下一个消息将会是2,以此类推)。

服务端做的事情是一样的,不过它使用“server_write_key”这个密钥。我们看到它的响应消息,包括泄露秘密的应用数据头。

这个消息解密后得到:

HTTP/1.1 200 OK
Date: Wed, 10 Jun 2009 01:09:30 GMT
Server: Server
...
Cneonction: close
Transfer-Encoding: chunked

这是一个普通的HTTP返回消息,它包含一个非描述性“Server: Server”HTTP头和一个来自Amazon负载均衡器的“Cneonction: close ”头,显然拼写有误。

TLS就在应用层之下。HTTP服务器上的软件可以就像发送非加密数据一样工作,而唯一的区别是它们(指软件)将数据写到一个库,由这个库统一做加密。OpenSSL 是TLS的一个非常流行的开源库。

在双方发送和接受加密数据的过程中,链接一直保持活动状态,直到任何一端发出一条“closure alert ”消息并接着关闭连接。如果在断开后的很短时间内重连,则可以重用前面协商好的密钥(前提是服务器还缓存着他们),这样就不需要公钥操作,否则,就需要重新执行一次完全的握手过程。

应用数据消息可以是任何信息,这点很重要。“HTTPS”特别的唯一原因是因为Web太流行了,还有其他基于TCP/IP的协议运行在TLS之上。例如,TLS也用于FTPSSMTP的安全扩展 。使用TLS肯定比你自己发明解决方案要好,另外,使用经得起仔细的安全分析 的协议,你能从中获益。

……顺利完工!

TLS RFC 中还包含很多我们这里遗漏的 内容,它是一份可读性非常好的规范。我们覆盖的内容是对Firefox和Amazon之间的220毫秒的舞蹈进行观察所看到的一条路径。这个过程很大程度 上受Amazon在ServerHello消息中所选择的TLS_RSA_WITH_RC4_128_MD5密码套件的影响,这是一个合理的选择,因为它 对速度的倾向比对安全的倾向更多一点。

如我们所看到的,如果有人能将Amazon的模数“n”分解成“p”,“q”,那么他就可以有效解密所有的“安全”流通,直到Amazon换掉它的证书。所以Amazon通过每年替换一次证书的方式来平衡这方面的担心。

其中有一个密码套件是“TLS_DHE_RSA_WITH_AES_256_CBC_SHA”,它使用Diffie-Hellman密钥交换算法 ,这个算法具有良好的 “正向安全 ”的特点。也就意味这如果某人破解了密码交换的数学原理,对于解密下一个会话也没有什么益处。这个算法的缺点是需要计算更大的数字,所以对于一个很忙的服务器来说计算成本更大。“高级加密标准”(AES )出现在我们提供的很多密码套件中。它与RC4 的不同在于它一次加密16字节的“数据块”而不是一个字节,由于它的密钥可以大到256比特,所以很多人认为它比RC4要更安全。

仅需220毫秒中,网络中两个端点连接到一起,提供了足够资料来信任对方,建立加密算法,并开始进行加密的信息传输。

我写了一个程序 对文章中所提及的握手协议走了一遍。

关于作者

Jeff 是一个软件开发工程师,他在http://www.moserware.com/ 上有一个自己的博客。

注意: 这篇文章最初发表在Moserware 上面,也就是Jeff Moser的博客里。

查看英文原文: The First Few Milliseconds of an HTTPS Connection

分享到:
评论
1 楼 waiting 2011-04-14  
挺详细的。

相关推荐

    HTTPS连接的前几毫秒发生了什么?

    HTTPS连接的前几毫秒内,一系列复杂的网络通信和安全机制迅速展开,确保了用户数据的安全传输。这个过程涉及到浏览器、服务器以及多个网络协议。在本文中,我们将深入探讨HTTPS连接建立初期的关键步骤。 首先,当...

    易语言毫秒级时间

    在编程领域,尤其是在需要精确时间控制的场景下,获取毫秒级时间是非常常见的需求。"易语言毫秒级时间"这个主题聚焦于如何在易语言中实现这样的功能。易语言,是中国本土的一种可视化编程语言,它以简洁的词汇和直观...

    易语言获取系统时间到毫秒

    本文将详细介绍如何使用易语言获取系统时间到毫秒,并探讨相关知识点。 首先,我们需要理解时间戳的概念。时间戳是自某个固定时间点(通常为1970年1月1日00:00:00 UTC)以来的秒数,而毫秒则是更精确的时间单位,...

    MFC毫秒级秒表

    本项目“MFC毫秒级秒表”利用了MFC库来实现一个高精度的时间测量工具,能够进行毫秒级别的计时操作,同时具备暂停、继续以及记录时间差的功能。 首先,我们要理解MFC中的时间处理机制。在MFC中,`CTime`类用于表示...

    小工具_毫秒级电脑计时器+毫秒级电脑时间显示.zip

    本文旨在介绍两个便捷而高效的工具——“毫秒级电脑计时器”与“毫秒级电脑时间显示”,它们将以小巧的体积和精准的功能,为相关领域的工作者提供专业的计时服务。 “毫秒级电脑计时器”是一个针对性极强的计时应用...

    VB 精确到毫秒的计时器 demo

    "VB 精确到毫秒的计时器 demo" 是一个专为此目的设计的示例程序,它利用了VB内置的日期和时间功能,实现了对时间间隔的精确测量,精度达到了毫秒级别。 在VB中,我们通常使用 `DateTime.Now` 属性来获取当前的系统...

    使用oracle计算系统当前时间的毫秒数

    ### 使用Oracle计算系统当前时间的毫秒数 在IT领域,特别是数据库管理和应用程序开发中,时间戳是非常重要的数据类型之一。它可以用来记录事件的发生时间、处理时间等关键信息。对于Oracle数据库用户而言,有时需要...

    C#实现毫秒转换成时分秒的方法

    在C#编程中,有时我们需要将时间以毫秒为单位的数值转换成更易读的时、分、秒格式。这种需求通常出现在处理高精度时间计算或者时间间隔表示的场景中。本文将深入探讨如何使用C#实现毫秒到时分秒的转换,并提供一个...

    把毫秒数转换为年月日时分秒

    毫秒数是时间戳的一种形式,它表示自1970年1月1日(UTC/GMT的午夜)开始所经过的毫秒数。在处理时间相关的程序和应用中,有时我们需要将这些毫秒数转换为人类可读的日期和时间格式,即年月日时分秒。这个过程涉及到...

    实用毫秒秒表(精度1毫秒)可自动缩放

    《实用毫秒秒表:高精度与自适应界面设计》 在信息技术日益发达的今天,时间的精确测量已经成为许多领域不可或缺的重要工具。特别是在编程、测试、游戏开发以及科学研究等领域,毫秒级的时间精度往往对结果的准确性...

    时间戳与时间互换精确到毫秒

    本文将深入探讨如何在毫秒级别上进行这种转换,并结合开源库"SanYe"来理解其背后的实现原理。 时间戳是一个表示特定时间点的数字,通常以自1970年1月1日(UTC/GMT的午夜)以来的秒数来计算,也被称为Unix时间戳。在...

    vc显示windows当前毫秒值

    在VC++环境中,尤其是使用Visual C++ 6.0(简称VC6)开发Windows应用程序时,有时我们需要获取系统当前的时间,而不仅仅是小时、分钟和秒,而是更精确的毫秒值。这通常涉及到Windows API中的时间函数或者C++标准库中...

    python 实现毫秒转换为时分秒

    在Python编程中,有时我们需要处理时间相关的数据,例如将毫秒转换为更易读的时分秒格式。这个任务在处理服务器返回的数据时尤其常见,因为服务器可能以毫秒为单位提供时间戳。本篇文章将深入探讨如何实现这一转换,...

    秒表计时,精确到毫秒

    在C#编程中,开发一个能够精确到毫秒的秒表计时器是一个常见的需求,尤其是在性能测试或者游戏开发等场景。本篇文章将详细介绍如何利用C#语言实现这样的功能,并结合给定的“秒表计时”项目进行讨论。 首先,我们...

    毫秒与时间互相转换-精确到毫秒

    功能:将1970-01-01 00:00:00以来的毫秒数转换为对应的timestamp时间类型,精确保留毫秒级精度! 参数:I_MILLISECONDS NUMBER 待转换的毫秒数 示例:select MILLISECONDS2TIMESTAMP(1397457489296) from dual; ...

    C3P0数据库连接池

    C3P0数据库连接池和jar包,因为jdbc没有保持连接的能力,一旦超过一定时间没有使用(大约几百毫秒),连接就会被自动释放掉。而每次新建连接都需要140毫秒左右的时间,所以耗费时间比较多。若使用C3P0连接池来池化...

    毫秒数转为时间格式

    毫秒数转为时间格式

    精确的毫秒表

    MFCApplication1.exe 毫秒表,精确到毫秒,方便最毫秒级的计时。

    C++获取当前时间精确到毫秒Ms

    在C++编程中,获取当前时间并精确到毫秒是一个常见的需求,特别是在处理高精度的时间戳或者性能测试等场景。本文将深入探讨如何在C++中实现这一功能,以及涉及的相关知识点。 首先,C++标准库提供了一个名为`...

Global site tag (gtag.js) - Google Analytics