- 第一章. 对SSL的基本概念和框架的介绍
- 第二章.对SSL握手协议的研究(part-1)
- 第三章.对SSL握手协议的研究(part-2)
- 第四章.对SSL握手协议细节和实现的介绍
- 第五章.对SSL记录协议细节和实现的介绍
- 第六章.对SSL的安全性分析
- 第七章.举例一种将usbkey融入java JSSE框架的解决方案
SL握手过程即完成身份认证和建立加密通道的过程,分为四种。
――Full Handshake : 全流程握手,C/S双方从无到有建立SSL连接;
――Resum session Handshake : C/S双方曾经建立过连接,但中途断了,SSL会话信息还有保留,只需要执行部分握手流程就可建立SSL连接;
――Server Re-negotiation Handshake : 已经建立了SSL连接,但server端为了某些原因(比如安全性)要求重新对密钥进行协商,也只需要执行部分握手流程;
――Client Re-negotiation Handshake : 已经建立SSL连接,但client端为了某些原因要求重新协商,只需执行部分握手流程。
Full Handshake
这里主要介绍全流程握手,握手步骤如下图所示,其他的握手过程都属于全流程的子集。
SSL握手协议总共有10中消息类型,类型名和枚举值如下:
- hello_request(0), client_hello(1), server_hello(2),certificate(11),
- server_key_exchange (12), certificate_request(13),server_done(14),
- certificate_verify(15), client_key_exchange(16),finished(20)
-
所有的握手消息有统一的结构:
- struct {
- HandshakeType msg_type;
- uint24 length;
- HandShakeMsg payload
- } Handshake;
-
客户端首先发 Client Hello消息到服务器端,服务器端收到hello消息后再发Server hello消息回应客户端。
Client Hello和Server Hello消息结构如下:
- struct {
- ProtocolVersion client_version;
- Random random;
- SessionID session_id;
- CipherSuite cipher_suites<0..216-1>;
- CompressionMethod compression_methods<0..28-1>;
- } ClientHello;
-
- struct {
- ProtocolVersion server_version;
- Random random;
- SessionID session_id;
- CipherSuite cipher_suite;
- CompressionMethod compression_method;
- } ServerHello;
选择SSL通信,当底层连接建立好 后会触发或调用SSL的初始化和握手,握手由Clinet端发出Client Hello消息开始。
Client Hello 消息
ProtocolVersion:消息中协议版本是两个byte长度分别表示主次版本,如若在JAVA中初始化SSLContext时候选择了SSLv3则主版本号是3,次版本号为0,若选择了TLSv1(TLSv1相当SSLv3的升级版)则主版本号是3,次版本号是1。
Random:随机数结构,由两部分组成,
- struct {
- uint32 gmt_unix_time;
- opaque random_bytes[28];
- } Random;
一个4字节的系统当前时间,一个28位长的随机数,在后面计算所有消息的摘要或计算主密钥时候会用到。(疑惑1 : 在java的JSSE实现版本中貌似只有一个4字节的系统当前时间,没有28位的随机数,奇怪了,怎么和非JSSE 版本实现正确握手的啊…)
Session ID : SSL会话ID标识一次会话用,可以重用。会话ID都是由服务器分配因此在全流程握手中client hello消息中的session id是空,用字节0表示。
CiphersuitList : 密钥套件列表,列表中包含了Client端支持的所有密钥套件。一个密钥套件定义了一个密钥规格,其中描述如下 内容:密钥交换算法,是否出口,对称加密算法,支持的最高对称密钥位数,MAC算法(或摘要算法)。一个ciphersuit用2个字节表示,下面列举JSSE支持的几个套件:
SSL_RSA_WITH_RC4_128_MD5 = 0x0004 /* 非对称加密算法或密钥交换算法为RSA,采用高强度128位对称加密算法RC4,摘要或MAC算法为MD5,不支持出口 */
SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014 /* 非对称加密算法或密钥交换算法支持RSA和DH,采用40位对称加密算法DES,摘要或MAC算法为SHA,可以出口 */
关于出口,美帝国主义要求要用他们的加密算法比如JDK中的JSSE,对称加密密钥长度不能超过40,非对称密钥长度不超过512,在国内只能使用这种低强度的玩意,赤裸裸的技术封锁啊。
这个加密套件列表长度不超过128K。
CompressionMethods: 客户端支持的压缩算法列表,填0表示空,JSSE会有默认算法。
Client Hello承载着这些信息被发送到Server端。
Server Hello 消息
ProtocolVersion :
服务端服务器拿出消息中的版本号,再看看自己支持的版本列表,选个两者都支持的最高版本号定为这次协商出来的SSL协议使用的版本。比如C端发过来ssl 3.0,而S端发现自己只支持ssl2.0,server就会选择SSL2.0作为这次协商版本,反之若Server支持SSL2.0,SSL3.0,TLS1.0,选两者都支持的最高版本SSL3.0。
Random : 产生的方式和Client Hello中相同。
Session ID: 服务端检测到传过来的session ID 是空或者检索session 列表没有发现传过来的session id就会新建一个, JSSE中Session ID是取系统时间的前32位(系统时间是long类型64位,只取其中32位)。
Ciphersuit : server端收到密钥套件列表后,将密钥套件一个个拿出来,经过几道检查,选择第一个通过检查的套件。JSSE实现的SSL协议中的检查项目有如下几项:检查服务端是否也支持这个套件,检查这个套件是不是被禁用(的确是支持某个套件,但出于某些原因被禁用了),检查套件是否符合出口限制,比如在国内加密强度128或256位的对称密钥在检查时候就会被认为不合法pass掉;若server端启用了双向认证,某些不支持双向认证的套件就会被pass掉。层层选拔,第一个通过的幸运儿光荣的成为密钥协商的成果,被放入Server Hello 消息中。
这样Server Hello消息组装好了,发出去告诉客户端协商的SSL版本和加密套件,并创建了一个会话,hello阶段结束。
服务发送证书(Server Certificates 可选)消息
在服务器发送完hello消息后接下来可以发送3个可选消息,服务器证书消息,服务器证书交换消息,客户端证书请求消息。服务器证书消息在全流程握手中一般是必须发的(疑惑2 :不知道除了session 重用外在什么情况下服务器端不需要发送证书过去? ),但是在会话重用的消息中就不需要再发。
服务器证书中包含公钥,发给客户端用来验证签名或在密钥交换时候给消息加密。证书消息是紧跟着ServerHello消息发送,证书消息中就是一个证书列表,证书应该转换成ASN.1 DER格式,不支持PKCS7格式。证书链中的证书挨个取下来放入列表中,按照次序服务器自己的证书放列表最前头,根CA证书放列表最后,证书链长度不超过16M。
服务端密钥交换消息(Server Key Exchange 可选)
说到这个不得不介绍下SSL密钥交换方式。安全加密通信是发送方将信息加密,接收方将信息解密,加解密用的密钥分两类:对称和非对称加密(具体概念google之)。
非对称加密 的最大优点是可以将一部分密钥公开,叫公钥(public key),这样通信双方交换密钥很简单,A和B通信,A有一个密钥对Pri-A和Pub-A,B也有一个密钥对Pri-B和Pub-B,AB互相交换自己公钥,A用Pub-B加密要发给B的消息,经过加密的消息就算被第三方窃取由于只有B有和Pub-B对应的私钥Pri-B,因此只有B才能解开消息,其他没有对应私钥的无法获得有效信息,B如果要向A发送消息同理也用Pub-A加密消息。
照这个办法这个安全通信的加密问题是否圆满解决了呢,世上痛苦的事十之八九,答案当然是否定的,非对称加密有致命的缺点就是加解密效率太低,不能应用在实际通信中应用,而对称加密 的特性正好和非对称加密相反,加解密都用同一个密钥,效率高,但通过网络交换密钥又比较困难,万一被哪个不安分的偷了去那这个加密信道对他来说形同虚设了。
这时一个聪明的办法就是在SSL握手期间用非对称密钥加密对称密钥发送到对方,接收方有私钥将消息解密,获得对称密钥,等握手结束后再使用已经交换好的对称密钥来家解密,这样即解决了非对称加密低效的问题又解决了对称密钥难交换的问题,问题终于圆满解决了。
聪明的人不止一个,又有种优秀的密钥交换算法被整出来了,DH (Diffie Hellman)算法,一种专门用来交换密钥的算法,简单的讲就是在通信前, A和B双方约定2个大整数p和g,其中1<g<p,这两个整数可以公开,然后A,B各自产生一个随机数Xa和Xb,用DH算法将p,g,Xa算出Ya,同样方法用p,g,Xb算出Yb,这个Xa和Xb各自要保管好,计算得到得Ya,Yb互相交换,这样A手头有Xa,Yb,B手头有Xb,Ya,这个神奇的DH算法能用A,B手头的两个数各自算出一个相同的密码,使用这个密码能产生一个相同的对称密钥。而第三方只获得p,g,Ya,Yb是无法算出Xa,Xb和密码的,经过计算这样A,B各自算出了共享的对称密钥再接下来的通信中对数据加解密。
再啰嗦一点有关对称密钥生成方面的细节, 对称密钥并不是直接生成的,是客户端产生一个预主密码(premaster),然后用密钥交换算法交给服务器端,两端根据这个预主密码计算出主密码,再用主密码生成对称密钥。过程比较曲折。对于RSA的密钥交换方式,预主密码是一个48位的随机数,而DH算法的预主密码相当于上边用Xa,Yb或Xb,Ya产生的那个共享密码。接下来的主密码生成方式和交换算法无关了,RSA和DH的都一样了,相当于密钥交换实际上是在交换那个Pre-master。
(疑惑3 : 为什么要搞出个预主密码和主密码来啊,客户端直接生成一个可用的对称密钥发给服务器端不就得了吗,折腾的,这点搞不懂,大牛指点一下啊 )
稍微介绍了下背景知识,回归正题,既然密钥交换算法有很多种那SSL握手期间用哪种呢,这个就是之前由选择的ciphersuit决定的,比如选择的是SSL_RSA_WITH_RC4_128_MD5 = 0x0004,那就是RSA的密钥交换算法即用非对称加密对称将密钥传送到对方,若选择的是SSL_DHE_RSA_EXPORT….那就使用DH交换算法。对不同的交换算法发过去的消息结构也会不一样,下面主要介绍RSA和DH两种方式的密钥交换。
RSA方式的密钥交换 :
在这种方式下,实际上服务器端的这个消息不是必须发送的,可选的。这个消息中包含了一个RSA公钥,公钥用两个参数表示,称为模数和指数。公钥已经在发送服务证书时候包含在证书里头交给客户端了,为啥还要发公钥过去啊?这个有如下几个原因:
Reason1 . 之前证书中的包含的是DSS(DSA) 公钥,此类公钥只能拿来签名,不能拿来加密,因此需要server端生成一对非对称密钥用来临时加密用,并将公钥塞在Server Key Exchange消息中发送过去。
Reason2 . 山姆大叔搞技术封锁,设置加密算法的出口限制,他规定RSA加密算法密钥长度不能超过512,如果超过512只能拿来签名,不能拿来加密。因此那些证书里头的RSA公钥长度超过512的不得不重新生成一对512长度的临时加密用的密钥,并将RSA公钥塞进消息体发出去。
Reason3 . 。。。我只知道上面两个原因,不排除还有其他的
这样说起来的话如果哥身处美国,不受出口限制,或哥证书中的公钥长度本来就没超过512就完全没必要发这个消息啦~因此这个消息是可选的,不是必须发的。
DH方式密钥交换
用DH算法产生整数p,g,和server端的Ys,将这三个参数塞进消息体。
为了防止消息被恶意篡改,Server Key exchange消息中还要包含一个对密钥参数的签名。
请求客户端证书消息(可选)
如果是SSL的双向认证的话,服务器端还会发出client cert request 消息,要求客户端发他自己的证书过来验证。使用JAVA JSSE,如果有如下设置说明启用了双向认证:
- SSLServerSocket.setNeedClientAuth(true)
此消息包含两部分内容:一个是server端支持的证书类型(RSA, DSA, ECDSA等。。。),另一部分是server端所信任的所有证书发行机构的DN(Distinguished Name)列表,客户端会用这些信息来筛选证书,以后会讲到。