学习笔记|一篇文章带你全面了解TLS 协议的全流程

目录

简介

发展历史

TLS 记录协议 

 TLS 握手协议

Client Hello

Server Hello

证书

服务端密钥交换

客户端证书请求

服务端问候结束

客户端证书

客户端密钥交换

密钥交换协议

客户端结束

服务端结束

密钥交换协议

告警协议

应用协议

X.509 证书

密钥交换

RSA 密钥

Diffie-Hellman 密钥交换


简介

TLS(英语:Transport Layer Security,缩写作TLS)传输层安全性协议,及其前身安全套接层(Secure Sockets Layer,缩写作SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。

网景公司(Netscape)在1994年推出首版网页浏览器,网景导航者时,推出HTTPS协议,以SSL进行加密,这是SSL的起源。IETF将SSL进行标准化,1999年公布第一版TLS标准文件。随后又公布RFC 5246 (2008年8月)与RFC 6176(2011年3月)。在浏览器、邮箱、即时通信、VoIP、网络传真等应用程序中,广泛支持这个协议。主要的网站,如Google、Facebook等也以这个协议来创建安全连线,发送数据。目前已成为互联网上保密通信的工业标准。 ​

发展历史

协议

年份

SSL 1.0

未知

SSL 2.0

1995

SSL 3.0

1996

TLS 1.0

1999

TLS 1.1

2006

TLS 1.2

2008

TLS 1.3

2018

TLS 记录协议 

传输层安全 (TLS) 记录协议使用握手期间创建的密钥保护应用程序数据。 记录协议负责保护应用程序数据并验证其 完整性 和来源。 它管理以下内容:

  • 将传出消息划分为可管理块,并重新组合传入消息。
  • 压缩传出块和解压缩传入块 (可选) 
  • 将 消息身份验证代码 (MAC) 应用于传出消息,并使用 MAC 验证传入消息。
  • 加密传出消息和解密传入消息。

记录协议完成后,传出加密数据将向下传递到传输控制协议 (TCP) 层进行传输。

TLS 记录协议是对 TLS 高层协议的一个简单封装,数据格式如下:

 

 enum {
    invalid(0),
    change_cipher_spec(20),
    alert(21),
    handshake(22),
    application_data(23),
    (255)
} ContentType;

struct {
    ContentType type;
    ProtocolVersion legacy_record_version;
    uint16 length;
    opaque fragment[TLSPlaintext.length];
} TLSPlaintext;
  • 内容类型: 1 字节,记录了里层协议(高层协议)的类型1
    • 0:非法字段
    • 20:密钥交换协议(change_cipher_spec)
    • 21:告警协议(alert)
    • 22:握手协议(handshake)
    • 23::应用层数据(application_data)
  • 主要版本: 1 字节,一般为 0x03代表 SSL 3
  • 次要版本: 1 字节,根据版本不同可能为 0x010x020x03分别代表 TLS 1.1、TLS 1.2、TLS 1.3(从这里也体现了 TLS、SSL 的一脉相传)
  • 数据长度: 2 字节
  • 消息验证码: 在很多地方提到记录层协议存在消息验证码字段,占 0、16、20 位长度

 TLS 握手协议

握手阶段使用 TLS 握手协议实现,握手协议格式如下

  • 握手类型: 1 字节
    • 1: 客户端问候(client_hello)
    • 2: 服务端问候(server_hello)
    • 4: 新会话(new_session_ticket)
    • 5: 早期数据结束(end_of_early_data)
    • 8: 加密插件(encrypted_extensions)
    • 11: 证书(certificate)
    • 13: 证书请求(certificate_request)
    • 15: 证书验证(certificate_verify)
    • 20: 握手结束(finished)
    • 24: 密钥更新(key_update)
    • 254: 消息哈希(message_hash)
  • 长度: 3 字节
  • 内容

根据不同的子协议,其格式会有不同

enum {
    client_hello(1),
    server_hello(2),
    new_session_ticket(4),
    end_of_early_data(5),
    encrypted_extensions(8),
    certificate(11),
    certificate_request(13),
    certificate_verify(15),
    finished(20),
    key_update(24),
    message_hash(254),
    (255)
} HandshakeType;

struct {
    HandshakeType msg_type;    /* handshake type */
    uint24 length;             /* remaining bytes in message */
    select (Handshake.msg_type) {
        case client_hello:          ClientHello;
        case server_hello:          ServerHello;
        case end_of_early_data:     EndOfEarlyData;
        case encrypted_extensions:  EncryptedExtensions;
        case certificate_request:   CertificateRequest;
        case certificate:           Certificate;
        case certificate_verify:    CertificateVerify;
        case finished:              Finished;
        case new_session_ticket:    NewSessionTicket;
        case key_update:            KeyUpdate;
    };
} Handshake;

整个握手阶段的流程如下:

Client Hello

请求必定是从客户端开始发起,因此第一步必定是客户端发出的 Hello 消息。

Client Hello 属于 TLS 记录协议的一种,因此其外层必然是

在这一步,客户端需要告诉服务端,自己的版本、支持的组件等信息

在原本的握手协议后,Client Hello 还有如下部分:

  • 版本: 2 字节,包含主版本和次要版本
  • 随机数: 32 字节,其中有 4 字节时间戳
  • 会话长度: 1 字节
  • 会话 ID
  • 加密套件个数: 2 字节
  • 加密套件列表: 每个套件 2 字节
  • 压缩方式个数: 1 字节
  • 压缩方式列表: 每个方法 1 字节
  • 插件个数: 2 字节
  • 插件列表:
    • 插件类型: 2 字节
    • 长度: 2 字节
    • 内容

如果是首次建立连接,这里的会话 ID 为空。而如果在一定时间内曾经建立过连接,双端将会缓存对应的信息,并复用之前的参数,这种不需要全部握手的形式,称为 SSL 会话恢复

Server Hello

当服务端收到客户端的问候后,需要返回自己的问候。从对于客户端支持的参数列表,服务端需要选择最安全的一个。如果某一个参数的列表在服务端均不支持,服务端无法支持,则需要返回握手失败。

再次讨论前面提到的 SSL 会话恢复,这里存在一个问题: 对于大流量的服务端,它们需要维护一个超大的会话列表。由于该部分是协议层的限制,难以进行优化。
为了解决该问题,可以使用 Session Ticket 将相关数据存储在客户端,由于内容由服务端进行加密,因此不会存在数据的泄露。

到这一步通信双端已经确定了后面需要使用哪些加密套件,以及相关的前置参数(两个随机数)

证书

SSL/TLS 体系服务端需要一个 ​ X.509 证书来验证自己的身份

通常而言,证书链的根证书,应该是所有人公认的根证书。根证书一般不直接签发通信用的证书,而是签发一些二级证书用于签发通信证书。在通信过程中,应该把除去根证书的证书链一起随着请求发送,以供对端验证。

到了这一步,客户端可以认为服务端是自己真正要请求的服务端,不是其他第三方假冒的(身份的确认,需要使用证书中的域名、ip 等信息)

服务端密钥交换

在前面的问候步骤,已经确定了使用的各种算法,在这里使用对应的方式(RSA、DH)以及两个随机数,进行密钥交换。

如果使用 DH 密钥交换,对于每种特定的计算方式,都有固定的基数3,如对于常见的 secp256r1,未压缩的 G 为:
 

04 
6B17D1F2 E12C4247 F8BCE6E5 63A440F2 
77037D81 2DEB33A0 F4A13945 D898C296 
4FE342E2 FE1A7F9B 8EE7EB4A 7C0F9E16 
2BCE3357 6B315ECE CBB64068 37BF51F5

而压缩后的 G 为:

03 
6B17D1F2 E12C4247 F8BCE6E5 63A440F2 
77037D81 2DEB33A0 F4A13945 D898C296

服务端根据设定好的基数,计算出公钥与私钥,将选取的曲线(或其他参数)以及公钥发送给客户端。

客户端证书请求

如果服务端还需要验证客户端身份,后续服务端需要发送 “Certificate Request” 要求进行证书请求

告诉客户端自己支持的证书类型,以及支持的根证书列表

服务端问候结束

至此,服务端问候结束

客户端证书

如果需要发送客户端证书,则客户端需要与服务端按照一样的步骤发送证书,并由服务端进行验证

客户端密钥交换

与服务端相同,客户端使用相同的参数计算出自己的公钥与私钥,并将公钥发送至对方

密钥交换协议

当双方协商密钥完成,客户端需要发送密钥协商协议,声明自己已经切换到协商好的加密套件,后续使用新的加密套件进行加密传输

客户端结束

握手阶段结束前,客户端需要发送 Client Finished 通知(使用协商好的加密套件加密)

服务端结束

针对客户端结束的回应,服务端需要发送服务端结束(还需要发送一个密钥协商协议,告知客户端加密套件切换)

密钥交换协议

密钥交换协议用于通知对端自己已经准备好切换到新的协议,该协议只有一个字节。但由于其实际上是冗余数据,因此在 TLS 1.3 已经被移除了

告警协议

告警协议是一个告诉对方自己发生错误,不会再接收数据的通知协议

 enum { warning(1), fatal(2), (255) } AlertLevel;
  
  struct {
      AlertLevel level;
      AlertDescription description;
  } Alert;

应用协议

实际要发送的数据存在于应用协议内加密发送

原本的应用层协议将会分段、计算 MAC、加密 后传输

X.509 证书

通常,证书以 .cer 格式存储,可以使用 openssl 查看证书内容

openssl x509 -in F2giRo29iMlijZg5U.cer -inform der -text -noout

一个证书包含如下关键信息

  • 签发者
  • 使用者
  • 签发时间
  • 过期时间
  • 序列号
  • 签名算法
  • 加密算法

要验证一个证书,需要递归地执行下述操作:

  1. 查看该证书是否过期?
  2. 如果证书未过期,查看签发者是否在信任的证书列表内
  3. 如果签发者在信任的证书列表内,则使用对应证书公钥验证该证书合法性;如果不在信任列表内,则需要从 1 开始检查该证书链的上一个证书(由服务端一起发送)
  4. 检查证书序列号是否在过期列表内

密钥交换

RSA 密钥

由于公私钥本身可以确保安全性,客户端在选定一个对称密钥密码后,可以使用证书中的公钥加密对称密码,传送给服务端后,服务端使用自己的私钥解密

在 RSA 体系本身安全的前提下,该方案可以确保安全性

Diffie-Hellman 密钥交换

如果不需要考虑身份验证,采用 Diffie-Hellman 可以在不安全信道进行密钥的交互

通信双方交换一个公共的基数 GG,素数 pp,而后双方分别设定一个随机数 aa 和 bb
双方交换自己的公共信息 A = (g^{a} \mod p)A=(gamodp) 和 B = (g^{b } \mod p)B=(gbmodp)

这样,双方可以使用对方的数据计算出一个相同的对称密钥 s = B^a \mod p = A^b \mod ps=Bamodp=Abmodp

由于 pp 是一个很大的素数,而求解离散对数非常复杂,因此即使被第三方观测到,仍然可以保证安全

但 Diffie-Hellman 体系本身不能确保身份验证,因此通常还需要 RSA 的配合

为了进一步增加计算难度,还有基于椭圆曲线的 Diffie-Hellman,简称 EC-DH

猜你喜欢

转载自blog.csdn.net/qq_22903531/article/details/131454448