DTLS 1.3协议握手解析

DTLS 1.3协议是基于TLS1.3协议的支持UDP的安全协议,提供加密性,和TLS1.3类似,它同样支持保密性,认证和完整性检验。DTLS与TLS最大区别是TLS无法支持UDP,因为UDP是无连接的协议。DTLS握手报文全部经UDP协议传输。DTLS1.3 与TLS1.3类似,都支持普通握手模式和预共享密钥握手模式(Pre-shared Key Handshake),而后者则是为快速恢复的0-RTT准备的模式。本文主要讲解普通握手模式,并省略初始ClientHello发送后服务端请求Cookie的过程。

根据RFC9147,DTLS1.3握手过程如下:

image-20250730161622006

客户端交换密钥生成 Key Gen

指定一个椭圆曲线算法生成客户端公钥。

ClientHello

32字节客户端随机数(Client Random)。

Session ID 字段在DTLS1.3中不再被使用,但仍然存在该字段。

Cookie字段不再被使用。

Cipher Suites提供了一系列客户端支持的加密套件,最常用的比如TLS_AES_128_GCM_SHA256

Compression Methods不再被使用,DTLS1.3不再支持压缩,所以用null表示。

在扩展字段中存在:

Key Share:客户端会选取服务器可能支持的密钥交换椭圆曲线,来计算出一个临时公钥用于生成握手密钥。

Supported Versions此处表明正在使用DTLS1.3版本,前面的版本号中会只写到DTLS1.2,DTLS1.3只能在此处标明。

Signature Algorithms:列出客户端支持的签名算法,比如ECDSA-SECP512r1-SHA512RSA-PSS-PSS-SHA384

Encrypt-then-MAC对协议无影响,因为在 DTLS 1.3 中,EtM 是唯一允许的模式。无论客户端是否声明支持 EtM,服务器都必须使用 EtM 处理所有记录层数据。因此,客户端发送的 EtM 支持声明不会改变会话行为。

Supported Groups:用来指定密钥交换算法采用的曲线类型和曲线。

服务端交换密钥生成 Key Gen

采用我们刚才指定的曲线生成服务端公钥。

ServerHello

32字节服务端随机数Server Random。

握手恢复数据(Handshake Reconstruction Data):用于对到达的数据进行重排,重组。

Session ID 字段在DTLS1.3中不再被使用,但仍然存在该字段。

Cipher Suite: 服务端从ClientHello中选择一个算法用于加密和完整性校验。

Compression Methods不再被使用,DTLS1.3不再支持压缩,所以用null表示。

Key Share 根据ClientHello中指定的曲线发送生成的公钥。在此公钥发送之后,客户端有能力计算出握手密钥,所以后面的握手内容会被加密。这和之前的TLS,DTLS1.2不同,之前的版本握手会被明文发送。

Supported Versions: DTLS1.3

服务端计算握手密钥 Secret Gen

根据指定的曲线算法,比如X25519曲线,来计算握手密钥,用于推导后面的会话密钥。

客户端计算握手密钥 Secret Gen

根据指定的曲线算法,比如X25519曲线,来计算握手密钥,用于推导后面的会话密钥。

服务器发送Encrypted Extensions

服务器发送多个证书Certificate

服务器发送本身的证书,包括主机名,公钥,一个第三方的签名声明这个证书的主机名确实拥有此证书的私钥。

此外,还发送一个可选的列表,包含了整个证书链,每一个证书都是签署了前一个证书,根信任证书被安装在客户端。

服务器验证内容CertificateVerify

也就是服务器用证书中的公钥(其实就是握手公钥)来加密一段内容,发送给客户端进行验证。

服务端发送握手结束Finished

为了验证握手成功并且内容未被篡改,服务器计算了所有握手哈希(从ClientHello到ServerCertificateVerity)并发送给客户端。

客户端发送握手结束Finished

客户端同样计算握手哈希(从ClientHello到Server Finished)并发送给服务端进行验证。

服务器计算会话密钥(Traffic Secret Gen or Application Key Calc)

服务器现在用刚才收到的客户端结束哈希握手密钥,用一系列密钥推导函数(HKDF),推导出会话密钥。

客户端计算会话密钥(Traffic Secret Gen or Application Key Calc)

客户端现在用刚才收到的服务端结束哈希握手密钥,用相同的密钥推导函数(HKDF),推导出会话密钥。

服务端发送ACK确认客户端Finished

数据用服务端刚刚推导出来的会话密钥加密ACK,客户端将进行解密检验。

客户端发送数据

客户端将用会话密钥发送加密数据。

服务端发送数据

服务端将用会话密钥发送加密数据。

服务端发送警告(Alert)有序关闭连接

Alert也会被用会话密钥加密。

带有关联数据的认证加密 AEAD(Authenticated Encryption with Associate Data)

在DTLS1.3和TLS1.3中,对于采用握手密钥加密的信息和会话密钥加密的信息,加密算法采用的都是AEAD方案。带有关联数据的认证加密(AEAD)原语是最常见的数据加密原语,适用于大多数需求。关联数据用来进行认证,如果信息被篡改可以被检测出。所以,AEAD有以下性质:

保密性:提供加密。

认证性:篡改密文,解密时会被检测出。

对称性(加密解密所用密钥相同)。

随机性:相同的明文执行两次AEAD加密会得到两个不同的密文,这得益于AEAD加密中加入了随机数使得密文随机化。

举例:用户ID可以被用来作为关联数据加密医疗数据,可以防止医疗数据对应用户被篡改。

AES128_GCM 通常是速度最快的模式,对消息数量和消息大小有最严格的限制。当超出这些明文和关联数据长度的限制时,AES128_GCM 会失败并泄露密钥材料。

附上AESGCM加密解密示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

# 生成密钥(AES-128 需要 16 字节)
key = AESGCM.generate_key(bit_length=128)
aesgcm = AESGCM(key)

# 生成 12 字节随机数
nonce = os.urandom(12)

# 明文和附加认证数据(AAD)
plaintext = b"Secret message that needs confidentiality and integrity"
aad = b"authenticated but not encrypted metadata"

ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
print(f"Ciphertext (hex): {ciphertext.hex()}")

decrypted = aesgcm.decrypt(nonce, ciphertext, aad)
print(f"Decrypted: {decrypted.decode()}")