目录
DTLS 概述
DTLS = Datagram Transport Layer Security
对于 TLS,相信大家都比较熟悉, 而 DTLS 的却不常见,这篇文章我们就来了解一下 DTLS 协议。
对于基于 TCP 的协议:
如果传输的数据有安全性要求,那么协议往往只需要简单的定义一下如何和 TLS 协议交互, 便能确保安全的传输数据。而通常这里的交互都非常简单,最简单的交互就是直接要求 TCP 链接建立之后,便开始协商 TLS 参数,接着所有的数据便可以直接通过 tls 加密上层协议的数据进行传输即可。
例如, HTTP 协议。 如果需要安全的传输 HTTP 协议,便需要使用 HTTPS 协议。 而 HTTPS 协议也只是要求建立 TCP 链接之后,开始 TLS 握手,握手成功之后,使用 TLS 协商出来的加密参数,对明文的 HTTP 协议数据包进行加密,然后再网络上传输,便完成了把 HTTP 变成 HTTPS。
对于基于 UDP 的协议:
由于 UDP 协议无法直接使用 TLS 协议,通常如果协议需要安全的传输数据,便需要在协议设计的初期便将数据安全考虑进来。 比如 Kerberos 协议,它就自己定义了一套数据加解密的协议。
但是这里就有弊端,就是每个协议都需要自己设计安全加密,而安全加密却是一门复杂的学问,一不小心,便可能设计一个并不安全的协议。
为此,DTLS 就是给 UDP 使用的 TLS 协议。
概述
DTLS 用于给基于 UDP 的协议提供安全的传输数据的功能。
如果你在使用一个 UDP 协议,但是这个协议自身没有提供安全的传输数据的功能,那么你可以考虑使用 DTLS。
DTLS 是 TLS 的衍生协议。 为了能和 UDP 一起工作,将 TLS 协议做了必要的修改。
最主要的就是: DTLS 不要求底层传输协议是 可靠的, 有序的。
面对的主要问题
将 TLS 直接应用于 UDP 上面临的主要问题是:UDP 数据传输会丢包,数据包会乱序。
而 TLS 的设计要求底层传输协议是可靠的,因此不能直接将 TLS 用于 UDP 协议上。
DTLS 想要对 TLS 做最小的改动,然后让 TLS 可以和 UDP 一起工作。
TLS 和 UDP 主要不兼容的部分有两个:
-
每个 record 的加解密不是独立的(independenty)。
因为 TLS 的完整性检测(integrity check) 依赖于隐式的 sequence number。 也就是说,如果我想要正确的检测 record N 的完整性,我需要收到所有 record N 之前的 record, 否则 record N 的完整性将无法正确检测。 -
TLS 握手层总是假定握手数据包是可靠传输的(有序,且不会丢包)。 对于 UDP,如果丢包或者乱序发生,那么 TLS 握手也会失败。
那么 DLTS 如何解决这些问题的?
丢包不敏感设计
TLS record 之间不独立,record 之间的主要关联为:
- 加解密上下文(Cryptographic context). 这里针对流式加密算法(stream cipher key stream).
- TLS 依赖隐式的 sequence number 来做反重放攻击(anti-replay) 和 MAC 校验。但是 UDP 不能保证有序,因此这里会出问题。
对于问题 1, DTLS 通过 禁用流式加密算法 解决。
对于问题2, DTLS 通过 添加显示的 sequence number 解决。
握手包的可靠传输设计
TL S握手的流程是有特定的流程的,要求发送什么包,然后接受什么包,必须按照预设的顺序进行。乱序会被认为是一个握手错误。 这很明显和 UDP 不兼容。
另外一个问题是,UPD 有最大包的限制,这里也需要特殊处理。
1. 丢包问题
毫无疑问,丢包就得重传咯。DTLS 使用一个简单的超时重传机制。
例如,我们作为客户端发送了 ClientHello,然后等待服务器响应 HelloVerifyRequest。 但是 Hello Verify Request 包丢了。
客户端在等待服务器响应 HelloVerifyRequest,知道本地记录的超时时间还没收到,它便认为发生了丢包,因此需要重传 ClientHello。
2. 乱序问题
在 DTLS 中,每个握手消息都有一个唯一的 sequence number。对方可以通过查看收到的握手消息中的 sequence number 来判断消息是否发生了乱序。
如果发生了乱序,可以选择缓存提前到达的消息或者丢弃它等待对方重传。
3. 重放检测
这个特定是可选的,不强制要求实现。 这里使用的技术来自 IPsec AH/ESP。
DTLS 和 TLS 的不同
前面我们也说了,DTLS 是TLS 的衍生品,这里我们来看看都有哪些不同处:
Record layer
这里相比于 TLS, record 数据包中多加了两个字段: epoch, sequence_number.
epoch
:
一个计数器。
这个计数器在每次 cipher 状态变化的时候需要递增。初始时为 0。
sequence_number
:
每个 record 都需要设置一个 sequence number. 从 0 递增。
对于每个 epoch, sequence number 是独立的。
重传时, sequence_number 依然需要递增。
Record 数据加密
Null cipher
: 和 TLS 1.2 相同。Standard Stream Cipher
: TLS 1.2 中只定义了 RC4 这个一个。 但是它不能再 DTLS 中使用。Block Cipher
: 和 TLS 1.2 相同。AEAD Ciphers
: 和 TLS 1.2 相同。
非法的 Record
在 TLS 中,非法的握手消息会导致握手流程失败。 但是在 DTLS 中,非法的握手消息应该被丢弃, 不要报错。
如果特定实现决定在这种情况下回复 Alert, 那么这个 Alert 是一个 fatal level alert.
Handshake Protocol
1. 如何应对拒绝服务攻击
DTLS 为此引入了 HelloVerifyRequest
握手包。
流程如下:
- 握手开始,客户端发送
ClientHello
到服务器 - 服务器收到
ClientHello
之后先不回复ServerHello
。而是回复一个HelloVerifyRequet
。 这个包中携带一个Cookie
。 - 客户端收到
HelloVerifyRequet
, 将其中的Cookie
附带到新的ClientHello
中再次发送到服务器。
因此,对 TLS 的 Client Hello 消息做了修改。新加了 Cookie 字段。
新的结构 HelloVerifyRequest 的结果如下:
2. 新的 Handshake 消息结构
为了处理 UDP 的丢包,乱序,重传,还有分帧, DTLS 对握手消息的结构做了如下调整:
message_seq
:
- 第一个握手消息的 message_seq 是 0, 握手流程中的其他消息中的 message_seq 以此增加。
- 重传的时候, message_seq 保持不变。 注意,这里和 record 中的 sequence number 不同,重传时, sequence number 是需要重新赋值的。
- DTLS 实现可以通过这个字段来判断是否发生了乱序,重传等。
例子:
3. 分帧
当某个消息的内容超过最大 UDP 包大小,需要将该消息分帧放入多个UDP 包中发送。
Handshake.fragment_offset
和 Handshake.fragment_length
用于分帧。
3. 重传
采用超时重传机制。没有规定必须如何实现,协议实现者可以自行设计。
DTLS 的协议我们就介绍完毕了,谢谢!