一、SSL概述

SSL连接分为两个阶段:握手和数据传输阶段。握手阶段对服务器进行认证并确立用于保护数据传输的加密密钥,必须在传输任何应用数据之前完成握手。一旦握手完成,数据就被分成一系列经过保护的记录进行传输。

1.1.握手

SSL握手有三个目的:

  • 客户端与服务器需要就一组用于保护数据的算法达成一致;
  • 他们需要确立一组由那些算法所使用的加密密钥;
  • 握手可以选择对客户端进行认证。

整个工作过程如下图:

ssl 连接azure redis ssl连接步骤_客户端

  • (1)客户端将它支持的算法列表+用于密钥产生过程的随机数发送给服务器;
  • (2)服务器从列表中的内容选择一种加密算法,并将其连同一份包含服务器公用密钥的证书发回给客户端;(证书包含了用于认证目的的服务器标识,同时,服务器还提供了一个作为密钥产生过程部分输入的随机数
  • (3)客户端对服务器的证书进行验证,并取出服务器的公用密钥(用于之后加密发给服务器的信息);然后产生一个称为pre_master_secret的随机密码串,并使用服务器的公用密钥对其进行加密。最后,客户端将加密后的信息发送给服务器;
  • (4)客户端与服务器根据pre_master_secret以及客户端与服务器的随机数独立计算出加密和MAC密钥
  • (5)客户端将所有握手信息的MAC值发送给服务器;
  • (6)服务器将所有握手信息的MAC值发送给客户端。

通过上述过程,就已经达到了SSL握手三个目的中的前两个目的了。其中,第一步和第二步实现了第一个目标(确立一种加密算法);第二步和第三步实现了第二个目标,在第2步中,服务端向客户端提供服务器公用密钥的证书,这样就允许客户端给服务器传送密码。经过第三步后,两端都知道了pre_master_secret(客户端知道是因为这是它产生的,服务端知道是因为它利用私密密钥解密得到的)。第四步,两端采用相同的密钥导出函数(KDF)来产生master_secret,最后再次通过KDF使用master_secret来产生加密密钥。最后两步是防止握手本身遭到篡改。

ssl 连接azure redis ssl连接步骤_SSL_02

对应着上一张图和这一张图,握手过程是通过一条或者多条消息实现的。

  • 第 1 步对应一条单一的握手消息, ClientHello。
  • 第 2 步对应一系列 SSL 握手消息,服务器发送的第一条件消息为 ServerHello,其中包含了它所选择的算法,接着再在 Certificate 消息中发送其证书。最后,服务器发送ServerHelloDone 消息以表示这一握手阶段的完成。需要 ServerHelloDone 的原因是一些更为复杂的握手变种还要在 Certificate 之后发送其他一些消息。 当客户端接收到 ServerHelloDone消息时,它就知道不会再有其他类似的消息过来了,于是就可以继续它这一方的握手。
  • 第 3 步对应 ClientKeyExchange 消息。
  • 第 5 与第 6 步对应 Finished 消息。 该消息是第一条使用刚刚磋商过的算法加以保护的消息。为了防止握手过程遭到篡改,该消息的内容是前一阶段所有握手消息的 MAC 值。然而,由于 Finished 消息是以磋商好的算法加以保护的,所以也要与新磋商的 MAC 密钥一起计算消息本身的 MAC 值。

二、SSL记录协议

在SSL中,实际的数据传输是使用SSL记录协议来实现的。SSL记录协议时通过将数据流分割成一系列的片段并加以传输来工作的,其中对每个片段单独进行保护和传输。在接收方对每条记录单独进行解密和验证。

在传输片段之前,通过计算数据的MAC来提供完整性保护。MAC与片段一起传输,并由接收实现加以验证。将MAC附加到片段的尾部,并对数据与MAC整合在一起的内容进行加密,以形成经过加密的负载。最后给负载装上头部信息。头信息与经过加密的负载的联结称为记录Record),记录就是实际传输的内容。

ssl 连接azure redis ssl连接步骤_客户端_03

2.1.记录头信息

记录头信息的工作就是为接收实现提供对记录进行解释所需的信息。在实际应用中是指三种信息:内容类型、长度和SSL版本

  • 长度字段:可让接收方知道它要从线路上读取多少直接才能对消息进行处理;
  • 版本号:确保每一方使用所磋商的版本的冗余性检查;
  • 内容类型:标识消息类型。

2.2.内容类型 

SSL支持四种内容类型:application_data、alert、handshake和change_cipher_spec。

  • application_data:使用SSL软件发送和接收的所有数据都是以application_data类型来发送的;
  • alert:主要用于报告各种类型的错误;
  • handshake:承载握手消息;
  • change_cipher_spec:标识记录加密以及认证的改变,一旦握手商定了一组新的密钥,就发送change_cipher_spec来指示此刻将启用新的密钥。

2.3.SSL规范语言

TLS和SSLv3都是使用同一种规范语言来描述他们各自的消息的。规范使用五种基本类型:opaque、uint8、uint16、uint24和uint32,分别对应无符号的8-、16-、24-和32-位整数,并在线路上以1、2、3或4字节序列加以表示。所有的数字都以“网络字节顺序”——高位在前来表示。

2.3.1.向量vector

向量是给定元素类型的元素序列,分为定长和变长向量。其中,定长向量用[]表示,变长向量用<>表示。长度以字节而不是元素个数为单位,所以uint16 foo[4]是表示是指两个16位整数,而不是4个!(长度为4字节,uint16位16位整数,即两字节整数,因此4个字节包含两个16位整数)。这样就允许解码器具有分层的结构,它可以将结构当做不透明的字符串(因为它知道它们的长度)来看待,并将其传送给另一层加以解析。

此外,可以通过长度的上限与下限、或只指定上限来表示变长向量。

// A string of between 1 and 20 bytes
opaque stuff<1..20>

// Up to 4 32 bit integers  
uint32 numbers<16>

2.3.2.枚举类型

枚举类型就是假定只有一系列特定值的字段,且每个值都有名字。

enum{red(1),blue(2),green(3)}colors;

指定一个名为colors 的类型,他们可以接收值red、blue和green,在线路上用整数1、2和3来表示。当在线路上对枚举变量进行编码时,用一个大到足以容纳其最大值的整数类型来表示,因此用一个字节的uint8来表示colors。也可以通过显式包含一个未命名的值来为一个枚举变量显式指定最大尺寸

enum{warning(1),fatal(2),(255)}AlertLevel;

2.3.3.结构

使用struct来构造结构化类型(与C语言类似):

struct{
    type1 field1,
    type2 field2,
    type3 field3,
    ……
}name;

 2.3.4.变体类型

可以将结构定义成依赖外部信息而变化的变体(Variant)类型,使用一种表面上看起来类似于C语言中的switch语句的结构来进行定义(类似于C语言中的union):

select(type){
    case value1:Type1
    case value2:Type2
    ……
}name;

2.4.握手消息结构

头信息为4字节长,包括一个字节的类型字段和3个字节的长度字段组成,长度字段表示剩余握手消息的长度(不包括类型与长度字段),余下的消息内容完全有赖于msg_type字段。

struct{
    HandshakeType msg_type;
    uint24 length;
    select(HandshakeType){
        case hello_request: HelloRequest;
        case client_hello: ClientHello;
        case server_hello: ServerHello;
        case certificate: Certificate;
        case server_key_exchange: ServerKeyExchange;
        case certificate_request: CertificateRequest;
        case server_hello_done: ServerHelloDone;
        case certificate_verify: CertificateVerify;
        case client_key_exchange: ClientKeyExchange;
        case finished: Finished;
    }body;
}Handshake;

enum{
    hello_request(0),client_hello(1),server_hello(2),
    certificate(11),server_key_exchange(12),certificate_request(13),
    server_hello_done(14),certificate_verify(15),client_key_exchange(16),
    finished(20),(255)
}HandshakeType;