一、SSL/TLS发展历史
现在最安全的证书协议:tls1.3
SSL(Secure Socket Layer,安全套接层)v1.0最早于由网景公司(Netscape,以浏览器闻名)在1994年提出,该方案第一次解决了安全传输的问题。1995年公开发布了SSLv2.0,该方案于2011年被弃用(
)。1996年发布了SSLv3.0(2011年才补充的RFC文档: ),被大规模应用,于2015年弃用( )。这之后经过几年发展,于1999年被IETF纳入标准化( ),改名叫TLS(Transport Layer Security Protocol,安全传输层协议),和SSLv3.0相比几乎没有做什么改动。2006年提出了TLS v1.1( ),修复了一些bug,支持更多参数。2008年提出了TLS v1.2( )做了更多的扩展和算法改进,是目前(2019年)几乎所有新设备的标配。TLS v1.3在2014年已经提出,2016年开始草案制定,然而由于TLS v1.2的广泛应用,必须要考虑到支持v1.2的网络设备能够兼容v1.3,因此反复修改直到第28个草案才于2018年正式纳入标准( )。TLSv1.3改善了握手流程,减少了时延,并采用完全前向安全的密钥交换算法。二、SSL/TLS现状
SSLv1.0~v3.0都已经不安全了,2015年SSLv3.0也被官方弃用,各大平台也都不推荐使用,像java从2015年1月20日开始JDK 8u31、JDK 7u75、JDK 6u91和更高版本将默认禁用SSLv3。TLSv1.0、TLSv1.1也都只是SSL的过渡版本,TLSv1.2是目前大多数终端、平台使用的标准协议。TLSv1.3是正在大力推广的协议,例如阿里云的服务器、又拍云的CDN、Chome和FireFox浏览器都已经支持TLSv1.3了。
协议 | 使用情况 |
---|---|
SSLv3.0以下 | 有安全问题,且已被废弃,不建议使用 |
TLSv1.0/v1.1 | 过渡版本,不建议使用 |
TLSv1.2 | 目前绝大多数都在使用,不知道选什么就选这个版本 |
TLSv1.3 | 最新的更快更安全的协议,如果有条件建议一步到位 |
SSL/TLS由于使用了加密算法,非常消耗CPU资源,因此应该尽可能将SSL在LB(负载均衡,Load Balance)层终结掉。终结的意思是负载均衡器对外提供SSL连接,对内提供TCP连接,把SSL剥离掉。这样传输更快,且后端服务不需要消耗CPU资源处理SSL了(后端服务一般都是VPC的,肯定安全)。常见的开源负载均衡器HAProxy、开源反向代理Nginx、收费负载均衡器AWS ELB等都是可以终结的。
三、SSLv3.0协议
3.1 SSLv3.0简介
3.1.1 分层
- 握手协议层(Handshake Protocol Layer):负责SSL的逻辑协议
- 记录层(Record Layer):负责SSL底层数据传输,为握手协议层提供支持
3.1.2 传输层协议选择
传输层一般都是TCP,需要可靠的传输层协议,所以UDP不应该被用来承载SSL。原话在RFC第一章Introduce:At the lowest level, layered on top of some reliable transport protocol (e.g., TCP [RFC0793])。
3.1.3 SSL协议
握手协议层有三个协议,分别是握手协议(Handshake Protocol)、更换加密规约协议(Change Cipher Spec Protocol)、告警协议(Alert Protocol)。
我们这里把更多的注意力放到握手协议层的握手协议上面。
3.2 SSLv3.0状态数据
SSLv3.0有两类状态数据。
第一类是会话状态(Session State),由握手协议创建,定义了一组可以被多个连接共用的密码安全参数,包括:
- 会话ID( session identifier)服务器选择的标记一个活动的或可恢复会话的字符序列
- 对等证书(peer certificate) x509.v3,可为空
- 压缩算法(compress method):加密前压缩数据的算法
- 密码规约(cipher spec):指定批量数据加密算法(如null、DES)和一个MAC算法(MD5、SHA),定义一些属性例如哈希大小(hash_size)
- 主密钥(master secret):服务端和客户端共享的48字节密钥
- 可恢复标记(is resumable):标记一个session的连接是否可恢复
第二类是连接状态(Connection State),记录了每个连接具体的一些密钥,包括:
- 服务端和客户端随机数(server and client random):每次连接客户端和服务端选择的字符序列
- 服务端写操作消息验证码密钥(server write MAC secret):用于服务端写数据时MAC操作的密钥
- 客户端写操作消息验证码密钥(client write MAC secret):用于客户端写数据时MAC操作的密钥
- 服务端写操作密钥(server write key):用于服务端数据加密和客户端解密的批量密码密钥
- 客户端写操作密钥(client write key):用于客户端数据加密和服务端解密的批量密码密钥
- 初始化向量(initialization vectors):当使用密码分组链接模式Cipher Block Chaining (CBC)时,为每个密钥维护初始化向量,这个字段会首先由SSL握手协议初始化,此后每个记录的最终密文块(ciphertext block)会被保留以用于后续记录
- 序列号(sequence numbers):每一方都会为每个连接发送和接收消息维护一个序列号,当一方发送或接收修改密码规约cipher spec消息时,会将相应的序列号重置为0,序列号是无符号长整型uint64的,不能超过2^64-1。
3.3 SSLv3.0 握手过程
在完成传输层握手后,进入SSL Handshake阶段:
(1)客户端发送Client Hello Message,表明自己要使用的SSL版本、以及相应参数。
- client_version:SSL版本。客户端会从高到低去尝试填入自己支持的SSL版本,例如这里就是SSLv3.0。
- random:客户端随机数。客户端的随机字符序列,用于后续协商密钥。
- session_id:本次会话ID。用于后面恢复会话。如果没有会话ID,则这里可以为空。
- cipher_suites:支持的加密套件列表。根据客户端想要使用的且自己支持的加密套件由高到低排序。
- compression_methods:压缩算法列表。根据客户端想要使用的且自己支持的压缩算法由高到低排序。可以为null,表示不要压缩。
一个加密套件包括四个部分(可参看RFC Appendix C. CipherSuite Definitions):
- Is Exportable:是否可以导出
- Key Exchange:密钥交换算法。用于密钥协商。
- Cipher:对称加密算法。用于信息加密。
- Hash:MAC的计算方法。用于完整性检验。
例如一个典型的加密套件SSL_RSA_WITH_RC4_128_SHA的含义为:
参数名 | 参数值 |
---|---|
Is Exportable | false |
Key Exchange | RSA |
Cipher | RC4_128 |
Hash | SHA |
(2)服务端反馈信息,并可选地要求验证客户端身份。
这些消息是通过一次传输就传递完的,不分开。
【Server Hello】反馈信息
- server_version:服务器支持的SSL版本。例如这里就是SSLv3.0。
- random:服务器随机数。服务器端的随机字符序列,用于后续协商密钥。
- session_id:本次会话ID。如果服务器找到了客户端传过来的session_id会话,并且可以恢复会话,那么这里会填入和Client Hello相同的session_id;否则这里将填入新session_id
- cipher_suite:密钥套件。服务器支持的,且是从客户端给的列表中选的密钥套件。
- compression_method:压缩算法。服务器支持的,且是从客户端给的列表中选的压缩算法。
【Certificate*】服务器证书
和所选密钥套件中密钥交换算法(Key Exchange)匹配的,用于客户端验证服务器身份和交换密钥的X.509证书。
【Server Key Exchange*】服务器密钥交换信息
如果服务器没有证书,或者服务器的证书仅用来签名(如DSS证书、签名RSA证书),或者使用的是FORTEZZA KEA密钥交换算法(现在已经不用了,不需要去了解),那么就需要发送这条消息。
【Certificate Request *】证书请求
要求客户端也发送它的证书,让服务端校验客户端的身份。
【Server Hello Done】服务器Hello阶段结束信息
一条简单的表明状态的空消息。
(3)客户端校验服务端身份
【校验服务器身份】
客户端会校验服务器发过来的证书的合法性,包括:
- 证书链的可信性
- 证书是否被吊销
- 证书是否处于有效期
- 证书的域名是否和当前访问域名匹配
如果发现证书不合法,客户端可以发起告警信息。
(4)客户端回应服务端
【Certificate*】客户端证书
如果服务器发送了“Certificate Request”,要求校验客户端身份,那么客户端需要回应自己的证书。如果客户端没有合适的证书,直接抛出告警信息让服务端处理(服务端的处理方式通常就是断开TCP连接)。和所选密钥套件中密钥交换算法(Key Exchange)匹配的,用于服务器验证客户端身份的X.509证书。
【Client Key Exchange*】客户端密钥交换信息
对于不同的密钥交换算法,密钥交换信息格式也不同:
密钥交换算法 | 密钥交换信息 |
---|---|
RSA | EncryptedPreMasterSecret |
Diffie-Hellman | ClientDiffieHellmanPublic |
Fortezza Kea | FortezzaKeys |
通常我们都会用RSA,而RSA的密钥交换信息就是加密的预主密钥(Encrypted Pre-Master Secret)。服务端会生成48字节的预主密钥,用服务器传过来的公钥证书加密该预主密钥。
【Certificate Verify*】客户端证书校验信息
用于提供客户端证书的显示校验信息,仅在具有签名功能的客户端证书(也就是除包含固定diffie-hellman参数的证书外的所有证书)之后发送。它是采用客户端的私钥加密的一段基于已经协商的通信信息得到的数据,服务器可以用公钥解密验证。
【Change Cipher Spec】更换加密规约
用于提示服务端这条连接以后都使用当前协商好的加密方式以及主密钥。
【Finish(Encrypted Handshake Message)】完成信息
当所有操作完成后,发送Finish信息。Finish信息包含了Handshake信息、主密钥的哈希散列(如SHA、MD5)数据,用于验证身份校验和密钥交换过程都成功了。Finish消息不要求收到回包,发送之后可以立刻进行加密应用数据传输。
(5)服务端回应客户端
服务端同样地发送Change Cipher Spec、Finish(Encrypted Handshake Message)消息,然后开始数据传输。
3.4 SSL的认证方式
SSL的认证方式有3种:
- 单向认证。客户端认证服务器。
- 双向认证。客户端认证服务器、服务器认证客户端。
- 匿名认证。不做任何身份校验。SSL反对使用该模式。
单向认证和双向认证相比,只是不需要客户端上传证书,其他没有区别。
3.5 复用会话
正常的SSL连接建立需要2个RTT(Round-Trip Time,往返时延),SSL协议规定可以复用会话,将其握手缩短到1个RTT。
如果服务端找到Client Hello中的session_id,那么直接复用该会话信息,发送的ServerHello中的session_id与ClientHello相同,并直接进入ChangeCipherSpec末尾阶段,完成握手。要注意,会话建立的连接会采用ClienHello中客户端随机数、ServerHello中客户端随机数生成的新的通信密钥,而不是上一个连接的旧密钥。
session_id不利于分布式,后面TLS在Client Hello的extension增加了session_ticket字段,类似于Web服务器Session和JWT Token的关系,具体情况具体使用。
3.6 SSL密钥计算
SSL定义了预备状态(pending state)和当前状态(current operating state)。
- 预备状态:包含本次握手过程中协商的各种算法和密钥
- 当前状态:包含记录层正在使用的各种算法和密钥
并且,客户端和服务器分别维护各自的读状态和写状态。
- 读状态:包含解密算法(对端加密算法的解密算法)、解压缩算法(对端压缩算法的解压缩算法)、MAC验证算法(对端MAC生成算法的验证算法),解密密钥(对端写密钥的解密密钥,一般都是对等密钥)
- 写状态:包含加密算法(己端加密算法)、压缩算法(己端压缩算法)、MAC生成算法(己端MAC生成算法),加密密钥(己端写密钥)
当客户端或服务器收到Change Cipher Spec消息后,会将预备读状态复制到当前读状态;
当客户端或服务器发送Change Cipher Spec消息后,会将预备写状态复制到当前写状态。
不同的握手密钥生成方式不同,这里以RSA为例(这里不用标准的变量名,而用带含义的,便于区别):
(1)客户端通过Client Hello发送客户端随机数Random_C,服务器也保存一份。
(2)服务器通过Server Hello发送服务器随机数Random_S和公钥证书public key,客户端也保存一份。
(3)客户端校验完服务器公钥证书后,将这个公钥同时用作加密用途。在Client Key Exchange阶段,利用公钥加密生成的预主密钥Pre-Master Secret。
(4)服务器收到预主密钥后,用自己的私钥解开得到明文预主密钥。
(5)客户端和服务器此时都有Random_C、Random_S、Pre-Master Secret,利用密钥导出函数KDF生成主密钥Master Secret。KDF一般是哈希函数,比如:
(6)再利用Master Secret生成密钥块keyblock,截取出6个参数,生成通信密钥。
分别截取出客户端写MAC密钥、服务器写MAC密钥、客户端写密钥、服务器写密钥、客户端IV、服务端IV(如果用了CBC的话)。
(7)发出ChangeCipherSpec消息,将这些密钥拷贝到记录层,用于读密钥、写密钥。例如,客户端写、服务器读用客户端写密钥;服务器写、客户端读用服务器写密钥,加密方式是对称加密。
配置域名bwb.com,以及对应的证书、私钥,配置SSL协议固定为SSLv3:
参考资料
1、
:似乎是信安的课程PPT,思路清晰
2、
:官方英文文档
3、
:很详细且正确
4、
:握手过程图很好
5、
:很详细
6、
:很详细
7、
8、
9、