参考文章:https://blog.csdn.net/alwaysrun/article/details/89076492
https://www.jianshu.com/p/fd0a624d0912
https://cloud.tencent.com/developer/article/1928677
文档:https://www.rfc-editor.org/rfc/rfc6347
https://www.rfc-editor.org/rfc/rfc5246
1.SSL/TLS 协议
1.1背景
首先从我们比较熟悉的 SSL/TLS 开始讲起。SSL(Secure Socket Layer) 和 TLS(Transport Layer Security) 简单理解就是同一件东西的两个演进阶段,同样都是在应用层和传输层之间加入的安全层,最早的时候这个安全层叫做 SSL,由 Netscape 公司推出,后来被 IETF 组织标准化并称之为 TLS。
TLS的动机从HTTPS讲起。HTTP 本身不具备加密的功能,所以也无法做到对通信整体(使用 HTTP 协议通信的请求和响应的内容)进行加密。按传输层 TCP/IP 协议族的工作机制,通信内容在所有的通信线路上都有可能遭到窥视。而HTTP 在传输数据的过程中,所有的数据都是明文传输,非常奔放,自然没有安全性可言。数据在公网上传输,容易被第三方获取,尤其特别是一些敏感数据,比如用户密码和信用卡信息等,一旦被第三方获取后果不堪设想。HTTPS 主要用于解决数据安全传输的问题,通过加密套件,让数据在网络传输过程中对第三方“不可见”(看见的是无意义的乱码信息)。在 HTTPS 中,原有的 HTTP 协议会得到 TLS (安全传输层协议) 或其前辈 SSL (安全套接层) 的加密。因此 HTTPS 也常指 HTTP over TLS 或 HTTP over SSL,即HTTPS = HTTP + SSL / TLS。
SSL/TLS 的作用是为了解决互联网通信中存在的三种风险:
- 窃听风险:第三方可以获知通信内容;
- 篡改风险:第三方可以修改通信内容;
- 冒充风险:第三方可以冒充他人身份参与通信。
SSL/TLS 协议能够做到以下这几点,从而解决上述的三种风险:
- 所有信息通过加密传播,第三方无法窃听;
- 具有数据签名及校验机制,一旦被篡改,通信双方立刻可以发现;
- 具有身份证书,防止其他人冒充。
1.2协议细则
(1)架构
SSL/TLS协议有一个高度模块化的架构,分为很多子协议,如下图所示。也可以简单地理解为,TLS协议分为两层,一层是记录层,一层是握手层。
- TLS记录层负责从更高层接收数据并对其进行处理。具体来说,记录层接收要传输的消息,将数据分成可管理的块,可选地压缩数据,应用MAC(消息认证码),加密,然后传输结果。接收到的数据被解密、验证、解压缩、重新组装,然后传递给更高层的客户端。 另外,为了允许TLS协议的扩展,记录层可以支持额外的记录内容类型。
- 在TLST握手层,中有三个子协议使用到了记录: handshake protocol、alert protocol、changecipher spec protocol。
SSL握手协议:包括协商安全参数和密码套件、服务器身份认证(客户端身份认证可选)、密钥交换;
SSL握手密钥参数更换协议:一条消息表明握手协议已经完成;
SSL告警协议:对握手协议中一些异常的错误提醒,分为fatal和warning两个级别,fatal类型的错误会直接中断SSL链接,而warning级别的错误SSL链接仍可继续,只是会给出错误警告;
(2)流程
SSL/TLS协议的执行过程被设计为两阶段:握手阶段和应用阶段。
- 握手阶段也称协商阶段,在这一阶段,客户端和服务器端会认证对方身份(依赖于PKI体系,利用数字证书进行身份认证),并协商通信中使用的安全参数、密码套件以及MasterSecret。后续通信使用的所有密钥都是通过MasterSecret生成。
- 在握手阶段完成后,进入应用阶段。在应用阶段通信双方使用握手阶段协商好的密钥进行安全通信。
随机数 |
生成方 |
生成报文 |
含义 |
A |
Client |
ClientHello |
C = pre-master-key master-key = function(A,B,pre-master-key) |
B |
Server |
ServerHello |
|
C |
Client |
—— |
1.3协议实现
(1)记录层
记录层将接收到的数据分片成大小不超过2^14字节的TLSPlaintext记录。这个分片过程是为了使数据块更易于管理和传输。重要的一点是,客户端的消息边界在记录层中并不保留,这意味着:
多个相同类型的客户端消息可以合并成一个TLSPlaintext记录。
一个消息可以被分片成多个记录。
struct { uint8 major; uint8 minor; } ProtocolVersion; enum { change_cipher_spec(20), alert(21), handshake(22), application_data(23), (255) } ContentType; struct { ContentType type; ProtocolVersion version; uint16 length; opaque fragment[TLSPlaintext.length]; } TLSPlaintext;
(2)握手层
ChangeCipherSpec
更改密码规范协议(Change Cipher Spec Protocol)用于标志加密策略的转换。它通过一个单字节的消息来通知对方,后续的通信将使用新协商的加密算法和密钥。
struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;
Alert
警报消息传达消息的严重性(警告或致命)和警报的描述。严重性为致命的警报消息会立即终止连接。
enum { warning(1), fatal(2), (255) } AlertLevel;
//warning:警告级别的警报,表示可能存在问题,但不需要立即终止连接。
//fatal:致命级别的警报,表示严重问题,必须立即终止连接。
enum {
close_notify(0),
unexpected_message(10),
bad_record_mac(20),
decryption_failed_RESERVED(21),
record_overflow(22),
decompression_failure(30),
handshake_failure(40),
no_certificate_RESERVED(41),
bad_certificate(42),
unsupported_certificate(43),
certificate_revoked(44),
certificate_expired(45),
certificate_unknown(46),
illegal_parameter(47),
unknown_ca(48),
access_denied(49),
decode_error(50),
decrypt_error(51),
export_restriction_RESERVED(60),
protocol_version(70),
insufficient_security(71),
internal_error(80),
user_canceled(90),
no_renegotiation(100),
unsupported_extension(110),
(255)
} AlertDescription;
struct {
AlertLevel level;
AlertDescription description;
} Alert;
Handshake
TLS握手协议的任务是生成会话状态的加密参数,该协议在TLS记录层之上运行。当TLS客户端和服务器首次开始通信时,它们会协商协议版本,选择加密算法,可选地相互认证,并使用公钥加密技术生成共享的秘密。
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;
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;
①HelloRequest
HelloRequest消息是由服务器发送给客户端的一个简单通知,指示客户端应该重新开始协商过程。
服务器可以在任何时间发送HelloRequest消息,但不应在客户端初次连接时立即发送。
HelloRequest消息结构非常简单,仅包含一个空的结构体。
struct { } HelloRequest;
②ClientHello
当客户端首次连接到服务器时,必须发送ClientHello作为其第一条消息。客户端还可以在收到HelloRequest后或出于自身需要重新协商现有连接的安全参数时发送ClientHello。
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
enum { null(0), (255) } CompressionMethod;
uint8 CipherSuite[2]; /* 加密套件选择器 */
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;
ClientHello消息包含以下主要部分:
- Random:一个包含当前时间和28字节随机数的结构,用于生成后续的加密参数。
- SessionID:一个可变长度的会话标识符,用于指示客户端希望重用的会话。
- CipherSuite:一个加密套件列表,按客户端的偏好顺序排列。
- CompressionMethod:一个压缩方法列表,按客户端的偏好顺序排列。
- Extensions:一个可选的扩展字段,用于请求服务器提供扩展功能。
加密套件列表包含客户端支持的加密算法组合,每个加密套件定义一个密钥交换算法、一个批量加密算法(包括密钥长度)、一个MAC算法和一个PRF算法。
③ClientKeyExchange
此消息总是由客户端发送。如果发送了客户端证书消息,它必须紧跟在客户端证书消息之后。否则,它必须是客户端在接收到ServerHelloDone消息后发送的第一条消息。
通过此消息,设置预主密钥,方法是直接传输RSA加密的秘密或传输Diffie-Hellman参数,使双方能够达成相同的预主密钥。
struct {
select (KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret;// RSA加密的预主密钥
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;// 客户端的Diffie-Hellman公钥值
} exchange_keys;
} ClientKeyExchange;
- 计算主密钥
对于所有密钥交换方法,使用相同的算法将pre_master_secret转换为master_secret。一旦计算出master_secret,pre_master_secret应从内存中删除。
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)//伪随机函数
[0..47];
- RSA
当使用RSA进行服务器认证和密钥交换时,客户端生成一个48字节的pre_master_secret,使用服务器的证书中的公钥加密,并发送给服务器。服务器使用其私钥解密得到pre_master_secret。然后双方按照上述规定将pre_master_secret转换为master_secret。此结构是ClientKeyExchange消息的变体,不是独立消息。
PreMasterSecret中的版本号是客户端在ClientHello.client_version中提供的版本,而不是为连接协商的版本。此功能设计用于防止回滚攻击。
struct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret;
struct {
public-key-encrypted PreMasterSecret pre_master_secret;//由客户端生成的随机值,用于生成主密钥
} EncryptedPreMasterSecret;
- Diffle-Herman
如果客户端使用了Diffie-Hellman密钥交换方法,客户端需要向服务器传递其Diffie-Hellman公钥值(Yc)。根据情况不同,Yc的传递可以是显式的或隐式的。
enum { implicit, explicit } PublicValueEncoding;
//implicit:客户端已经发送了包含Diffie-Hellman密钥的证书,因此不需要再次发送Yc。客户端密钥交换消息将被发送,但内容为空。
//explicit:需要发送Yc。
struct {
select (PublicValueEncoding) {
case implicit: struct { };
case explicit: opaque dh_Yc<1..2^16-1>;
} dh_public;//客户端的Diffie-Hellman公钥值(Yc)
} ClientDiffieHellmanPublic;
④CertificateVerify
此消息用于对客户端证书进行显式验证。此消息仅在客户端证书具有签名能力(即,除包含固定Diffie-Hellman参数的证书外的所有证书)后发送。发送时,它必须紧跟在客户端密钥交换消息之后。
struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length];
}
} CertificateVerify;
这里的handshake_messages指的是从客户端hello开始到(但不包括)此消息为止发送或接收的所有握手消息,包括握手消息的类型和长度字段。这是迄今为止交换的所有握手结构(如第7.4节所定义)的连接。注意,这要求双方要么缓存消息,要么为所有潜在的哈希算法计算运行哈希,直到CertificateVerify计算时。服务器可以通过在CertificateRequest消息中提供有限的摘要算法集合来最小化此计算成本。
⑤Finished
完成消息(Finished)是TLS握手过程中的最后一个握手消息,用于确认整个握手过程的完整性和正确性。它是第一个使用刚协商的加密算法、密钥和秘密进行保护的消息。
完成消息总是在更改密码规范消息(ChangeCipherSpec)之后立即发送。这个顺序是必须的,因为完成消息是用新协商的加密算法和密钥保护的。
struct { opaque verify_data[verify_data_length]; } Finished; //verify_data是一个通过伪随机函数(PRF)计算的验证数据 PRF(master_secret, finished_label, Hash(handshake_messages))[0..verify_data_length-1]; /* finished_label: 客户端发送的完成消息使用字符串:"client finished"。 服务器发送的完成消息使用字符串:"server finished"。 */ /* handshake_messages: 这是到目前为止交换的所有握手消息的哈希值,不包括HelloRequest消息和记录层头。 */
2.DTLS(1.2)
2.1背景
DTLS 的全称为 Datagram Transport Layer Security,从名字上就可以看出它和 TLS 的区别就在于多了一个“Datagram”,因为我们把使用 UDP 传输的报文叫做 “Datagram”,所以这个名字也就意味着 DTLS 是适用于 UDP 传输过程的加密协议。 TLS 1.2 及之前都没有尝试解决 DoS 攻击的问题,直到 TLS 1.3 才通过加入了 HelloRetryRequest 和 Cookie 来解决 DoS 攻击的问题。而相对 TCP 来说,UDP对 DoS 攻击更加敏感,因此 DTLS 在 1.0 版本就加入了 HelloVerifyRequest 和 Cookie,用于服务端对客户端的二次校验。单对比 TLS 1.2,DTLS 1.2 大部分步骤都是一样的,只是在服务端多了一步 HelloVerifyRequest,客户端因此也多了第二次的 ClientHello
2.2协议细则
(1)架构
(2)流程
DTLS使用与TLS相同的所有握手消息和流程,但有三个主要变化:
- 添加了无状态的cookie交换,以防止拒绝服务攻击。
- 修改了握手头部以处理消息丢失、重新排序和DTLS消息分片(为了避免IP分片)。
- 为了处理消息丢失而设置了重传定时器。
服务端在首次收到客户端发送的 Client Hello 之后,只会生成一个 Cookie,不进行任何其他的操作,并给客户端发送 HelloVerifyRequest 消息,带上这个 Cookie。只有当客户端重新发送一次 Client Hello,并带上服务端发送的这个 Cookie 后,服务端才会继续握手过程。
2.3协议实现
(1)记录层
略
(2)握手层
为了支持消息丢失、重新排序和消息分片,DTLS修改了TLS 1.2的握手头部结构:
enum {
hello_request(0), client_hello(1), server_hello(2),
hello_verify_request(3), // 新字段
certificate(11), server_key_exchange(12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255) } HandshakeType;
struct {
HandshakeType msg_type;
uint24 length;
uint16 message_seq; // 新字段
uint24 fragment_offset; // 新字段
uint24 fragment_length; // 新字段
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case hello_verify_request: HelloVerifyRequest; // 新字段
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;
-
消息序列号(
message_seq
):每条握手消息都有一个序列号,以确保消息的完整性和顺序。序列号从0开始,每发送一条消息递增。这有助于在消息重传时维持顺序。
在每次握手中,每方传输的第一条消息的message_seq
总是为0。每当生成新消息时,message_seq
的值增加1。
-
片段偏移(
fragment_offset
)和片段长度(fragment_length
):这两个新字段允许将较大的握手消息分成多个片段进行传输。这是因为UDP等数据报协议不保证单一消息的完整性,所以需要手动处理消息的分片和重组。 -
新的握手类型(
hello_verify_request
):这是DTLS特有的消息类型,用于验证客户端,作为防止DoS攻击的一部分。
①ClientHello
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
opaque cookie<0..2^8-1>; // 新字段
CipherSuite cipher_suites<2..2^16-1>;
CompressionMethod compression_methods<1..2^8-1>;
} ClientHello;
②Certificate
随机数 |
生成方 |
生成报文 |
A |
Client |
ClientHello |
B |
Server |
ServerHello |
C |
Client |
—— |