随机密码串(Pre-Master Secret)现在我们已经了解了Amazon.com的各项要求,并且知道了公共解密密钥e和参数n。在通信过程中的任何一方也都知道了这些信息(佐证就是我们通过Wireshark获得了这些信息)。现在我们所需要做的事情就是生成一串窃密者/攻击者都不能知道的随机密码。这并不像听上去的那么简单。早在1996年,研究人员就发现了网景浏览器1.1的伪随机数发生器仅仅利用了三个参数:当天的时间,进程ID和父进程ID。正如研究人员所指出的问题:这些用于生成随机数的参数并不具有随机性,而且他们相对来说比较容易被破解。 因为一切都是来源于这三个随机数参数,所以在1996,利用当时的机器仅需要25秒钟的时间就可以破解一个SSL通信。找到一种生成真正随机数的方法是非常困难的,如果你不相信这一点,那就去问问Debian OpenSSL的维护工程师吧。如果随机数的生成方式遭到破解,那么建立在这之上的一系列安全措施都是毫无意义的。 在Windows操作系统中,用于加密目的随机数都是利用一个叫做CryptGenRandom的函数生成的。这个函数的哈希表位对超过125个来源的数据进行抽样!火狐浏览器利用CryptGenRandom函数和它自身的函数来构成它自己的伪随机数发生器。(译者注:之所以称之为伪随机数是因为真正意义上的随机数算法并不存在,这些函数还是利用大量的时变、量变参数来通过复杂的运算生成相对意义上的随机数,但是这些数之间还是存在统计学规律的,只是想要找到生成随机数的过程并不那么容易)。 我们并不会直接利用生成的这48字节的随机密码串,但是由于很多重要的信息都是由他计算而来的,所以对随机密码串的保密就显得格外重要。正如我之前所预料到的,火狐浏览器对随机密码串的保密十分严格,所以我不得不编译了一个用于debug的版本。为了观察随机密码串,我还特地设置了SSLDEBUGFILE和SSLTRACE两个环境变量。 其中,SSLDEBUGFILE显示的就是随机密码串的值:
需要注意的是,这串数字从各种意义上来说都不是真正的随机数,就拿它的前两位来说:这就是根据TLS协议约定的TLS版本号(0301)。
密码交换(Trading Secret)我们现在需要做的就是计算出Amazon.com所要求的密码。因为Amazon.com希望使用“TLS_RSA_WITH_RC4_128_MD5”加密组,所以我们使用RSA加密算法进行这一过程。你可以将这48字节的随机密码串作为初始参数,但是根据公共密钥密码标准(PKCS)#1 v1.5中的注释,我们需要用随机数据将随机密码串填充到实际要求的参数大小(1024位二进制/128字节)。这样的话攻击者想要破解我们的随机密码串就难上加难了。这也是我们保障自己安全的最后一道防线,以防我们在前面的步骤中犯了诸如重复使用密码这样的低级错误。如果我们重复使用了随机密码串,由于使用了随机数填充,窃密者在网络中拦截的也会是两个不同的值。 同样的,我们很难直接观察到火狐浏览器中的这一过程,所以我不得不在填充随机数的函数中增加了debug的语句,使我们能够观察这一过程:
在这个例子中,完整的填充后的随机密码串为: 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 1C0A B1 95 50 29 BE 02 AD E6 AD 6E 11 3F20 C4 66 F0 64 22 57 7E E1 06 7A 3B 火狐浏览器使用这个值计算出 ,我们可以看到它显示在“客户端交换密钥”(Client Key Exchange)的记录中: 在这个过程的最后,火狐浏览器会发送一个不加密的信息:一条“Change Cipher Spec”记录: 通过这种方式:火狐浏览器要求Amazon.com在后面的通信过程中使用约定的加密方式传输信息。
获得主密钥(Master Secret)如果我们正确完成了之前的过程,并且各方都获得了48字节(256二进制位)的随机密码串。从Amazon.com的角度来看,这里还有一些信任问题:随机密码串是由客户端生成的,并没有将任何服务器信息或者之前约定的信息加入其中。这一点,我们会通过生成主密钥的方式加以完善。根据协议规范约定,这个的计算过程为:
pre_master_secret就是我们之前传送的随机密码串,”master secret”是一串ASCII码(例如:6d 61 73 74 65 72……),再连接上在客户端问候和服务器问候(来自Amazon的)的信息。 PRF是在规范中约定的伪随机函数,它将密钥、ASCII码标签、哈希值整合在一起。各有一半的参数分别使用MD5和SHA-1获取哈希值。这是一种十分明智的做法,即使是想要单单破解相对简单MD5和SHA-1也不是那么容易的事情。而且这个函数会将返回值传给自身直至迭代到我们需要的位数。 利用这个函数,我们生成了48字节的主密钥: 4C AF 20 30 8F4C 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
生成各种密钥现在,各方面已经有了主密钥,根据协议约定,我们需要利用PRF生成这个会话中所需要的各种密钥,称之为“密钥块”(key block):
密钥块用于构成以下密钥:
因为我们使用了类似于高级加密标准(AES)的密码流代替了分组密码我们就不需要初始化向量(IVs)了。因此我们只需要双方的两个16字节(128二进制位)的消息认证码(Message Authentication Code,MAC),因为MD5的哈希值就是16字节的。此外,双方也需要16字节(128二进制位)的RC4码。所以我们总共需要从密码块获得2*16 + 2*16 = 64字节的数据。 运行PRF,我们能得到以下值:
准备加密!客户端最后一次送出的握手信息是“结束信息”。这条信息保证了没有人篡改握手信息,并且我们已经知晓所必须的密钥。客户端将整个握手过程的全部信息都放入一个名为“handshake_messages”的缓冲区。我们能通过伪随机函数利用主密钥、“client finished”标签、MD5和SHA-1的哈希值生成12字节的“区别数据”(verify_data):
我们在这个结果前面加上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)。这个序号的作用就是为了阻止攻击者在数据流中间插入之前被其截获的信息。如果发生了这样的攻击,序号就能清楚的警告我们数据中的异常。同样的,这个序号也能帮助我们发现攻击者从数据流中剔除的数据。 剩下的工作只剩下啊加密这些数据了!
RC4加密我们之前协商过的密码组是“TLS_RSA_WITH_RC4_128_MD5”。这就意味着我们需要使用RC4(Ron`s code 4)加密规则进行数据流的加密。罗纳德李威斯特(Ron Rivest)开发了这种基于一个256字节的Key产生随机加密效果的算法。这个算法简单到你几分钟就能记住。 首先,RC4生成一个256字节的数组S,并用0-255填充。接下来的工作就是将需要将KEY混合插入进数组中并反复迭代。你可以编写一个状态机,利用它来阐释随机字节。为了产生随机字节,我们需要将S数组打乱,如图所示: 为了加密一个字节,我们将其与随机字节进行异或运算。记住:一位二进制数与1作异或运算会翻转(译者注:即1^1=0;1^0=1)。因为我们利用的是随机数,所以从统计学的角度来讲,有一半的数被翻转。这种随机翻转的现象就是我们加密数据的有效方法。正如你所见,这并不复杂,而且计算速度十分快,我认为这也是Amazon.com选择它的原因之一。 记得我们有“client_write_key”和“server_write_key”吗?这就意味我们需要两个RC4实例:一个用来加密浏览器向服务器传送的数据,一个用来解密服务器向浏览器传送的数据。 “client_write_key”最初的几个字节是7E 20 7A 4D FE FB 78 A7 33 …如果我们对这些字节和未加密的数据头以及版本信息(“14 00 00 0C98 F0 AE CB C4 …”)进行异或运算,我们就能得到在Wireshark中看到的加密信息了: 服务器端做的几乎是相同的事情。它们发送了一个密钥协议的说明和一个包含全部握手过程的结束信息,其中有结束信息的解密版本。因此,这种机制就保证了客户端和服务器能成功的解密信息。
欢迎来到应用层!现在,从我们点击了按钮之后已经过去了220毫秒,我们终于为应用层做好了准备!现在,我们发送的普通的HTTP数据流会通过TLS层的加密实例进行加密,在服务器的解密实例进行解密。而且TLS会对数据进行哈希校验,以保证数据内容的准确性。 在这个时候,整个的握手过程就结束了。我们的TLS记录内容现在有了23条(0×17)。加密数据以“17 03 01”开头,表示了记录类型和TLS版本,后面紧跟着加密数据的大小和哈希校验值。 加密的数据的明文如下:
在Wireshark中显示如下: 唯一有趣的地方是序号是按照记录来增长,这条记录是1,下一条就是2。 服务器端利用“server_write_key”做着同样的事情。我们能看到服务器的相应结果,包括程序开头的指示位: 解密后的信息如下:
这就是一个来自Amazon负载平衡服务器的普通HTTP回应:包含了非描述性的服务器信息“Server: Server”和一个拼错了的“Cneonction: close”。 TLS层在应用层的下面,所以软件和服务器能够像正常的HTTP传输那样进行工作,唯一的区别就是传输的数据会被TLS层进行加密。 OpenSSL是一个应用很广的TLS开源库。 整个连接会一直保持,除非有一方提出了“关闭警告(closure alert)”并且关闭了连接。如果我们在连接断开后的短时间内再次提出连接请求,我们可以使用之前使用过的key来进行连接,从而避免一次新的握手过程。(这个要取决于服务器端key的有效时间。) 需要注意的是:应用程序可以发送任何数据,但是HTTPS的特殊之处在于WEB应用的广泛普及。要知道还有非常多的基于TCP/IP并且使用TLS进行数据加密的协议(如FTPS,sSMTP)。使用TLS要比你自己发明一种是数据加密方案便捷的多。况且,你所使用的安全协议一定要足够安全。
…完工!TLS RFC的文档包含了更多的信息,有需要的朋友们可以自己查阅,我们在这里只是简单的介绍了其中的过程和原理,观察了这220毫秒内发生在火狐浏览器和Amazon服务器之间发生的故事:由Amazon.com基于速度和安全的综合考虑选择的“TLS_RSA_WITH_RC4_128_MD5”密码组在HTTPS连接建立过程中的全部流程。 正如我们所看到的那样,如果有人能对Amazon服务器的参数n进行因式分解得到p和q的话,那他就能破解全部的基于亚马逊证书的安全通信。所以Amazon为这个参数设置了有效期以防止这种事情的发生: 在我们提供的密码族中,有一组密码组“TLS_DHE_RSA_WITH_AES_256_CBC_SHA”使用了Diffie-Hellman密钥交换,并因此能提供良好的前向安全特性。这就意味着如果有人破解了交换密钥的数学运算方式,他们也不能利用这个来破解其他的会话。但是他的一个劣势在于其运算需求更大的数字和更高的运算能力。AES算法在很多密码组中都出现了,它与RC4的不同之处在于它每次使用的是16字节的“块”而RC4使用的是单字节。因为其key最高能到256位二进制位,所以一般认为它比RC4的安全性更高。 在短短的220毫秒的时间里,两个节点通过互联网连接起来,并且利用一系列手段建立起了互信机制,构建了加密算法,进行加密数据的传输。 正是因为如此,我们故事的主人公才能在Amazon上买到他想要的牛奶! (译者注:作者所有相关的程序已经提交到Github上,地址:https://github.com/moserware/TLS-1.0-Analyzer/tree/master) |