DTLS数据包传输层安全性协议详解

目录

1、DTLS的特点

2、DTLS相比 TLS做了一些改进

3、DTLS的消息格式

3.1、记录层

3.2、传输层映射

3.3、记录负载保护

3.4、握手消息格式

3.5、握手交互消息内容


        互联网先驱们最开始在设计互联网协议时主要考虑的是可用性,安全性是没有考虑在其中的,所以传输层的 TCP、UDP协议本身都不具备安全性。

       SSL/TLS协议是基于 TCPsocket,在传输层和应用层之间构建了一个端到端的安全通道,对传输数据进行加密,保证数据的安全传输。但 SSL/TLS协议不能用于 UDP协议,UDP也有安全传输的需求,于是就产生了 DTLS协议(DatagramTransportLayerSecurity)。

       DTLS的作用为给 UDP提供端到端的安全通道,就像 SSL/TLS对 TCP的作用,并且 DTLS尽可能参考了 SSL/TLS协议的安全机制,在具体实现上复用了 70%的 TLS代码。

1、DTLS的特点

       UDP协议是不面向连接的不可靠协议,且没有对传输的报文段进行加密,不能保证通信双方的身份认证、消息传输过程中的按序接收、不丢失和加密传送。而 DTLS协议在 UDP提供的 socket之上实现了客户机与服务器双方的握手连接,并且在握手过程中通过使用 PSK或 ECC实现了加密,并且利用 cookie验证机制和证书实现了通信双方的身份认证,并且用在报文段头部加上序号,缓存乱序到达的报文段和重传机制实现了可靠传送。

       在握手完成后,通信双方就可以实现应用数据的安全加密和可靠传输。DTLS可以使用
空算法套件,但是不能是支持 RC4流算法,因为 RC4不能随机使用。

2、DTLS相比 TLS做了一些改进

  • 1)禁止流加密。这样可以保证记录间加密上下文的独立性。
  • 2)给记录增加一个显示的序列号字段用来做重放攻击保护和记录数据认证。
  • 3)给握手包加入重传机制。为了 requeset启用一个 timer,如果没有收到 response,而 timer超时,那就重传 request.
  • 4)保证握手包的顺序。每个握手包是有序列号的,如果不是期望收到的握手包,那么把它放入队列,在将来一旦所有缺失的消息都收到后再进行处理。如果是,立刻处理。
  • 5)消息的 size。UDP包如果大于 1500会被分片。所以每个 DTLS握手报文包含一个段偏移和段长,如此拥有一个握手消息所有字节的接收者便可还原被分片的握手报文。
  • 6)重放保护。维护一个接收报文的 bitmapwindow。过老的报文或者收到过的包都被扔掉。重放探测特性是可选的,因为包的重复并不总是恶意的,可能会因为路由错误而出现。应用可以可信地探测重复报文并相应地修改他们的数据重传策略。

3、DTLS的消息格式

3.1、记录层

       DTLS记录层与 TLS1.2及其相似。唯一的变化是在记录中包含了一个显式序列号。这个序列号允许接收者正确地验证 TLSMAC。DTLS记录格式显示如下:

struct
{
    ContentTypetype;
    ProtocolVersionversion;
    uint16epoch; //Newfield
    uint48sequence_number; //Newfield
    uint16length;
    opaquefragment[DTLSPlaintext.length];
}DTLSPlaintext;

上述结构体各字段的详细说明如下:
1)type
与 TLS1.2记录层的 type域相同。
2)epoch
一个计数值,每次加密状态改变时都会加 1。主要是用来区别在一个多次重新协商的情况,多个记录报文可能会具有相同的序列号,因此再用这个域来区分,接收者可以用来区分不同的包。Epoch初始值为 0,每发送一个 changecipherspec消息后都加 1。
3)Sequence_number
本记录的序列号。
4)Length
与 TLS1.2记录层的 length域相同。如 TLS1.2中一样,这个长度不超过 2^14。DTLS使用了一个显式序列号,而不是一个隐式的,这个序列号位于记录中sequence_number域。对于每个 epoch序列号都单独维护,对于每个 epoch每个sequence_number都会被初始化为 0.例如,如果一个 epoch为 0的握手消息被重传了,它可能会拥有一个在一个 epoch1的消息之后的序列号,即使 epoch1的消息被首先重传。需要注意的是在握手阶段需要多加关注以确保重传消息使用了正确的 epoch和密钥材料。

3.2、传输层映射

       每个 DTLS记录必须能够放入单个数据报内。为了避免 IP分片,DTLS记录层的 client应该尝试改变记录的大小以使其能适应从记录层获得的任何路径 MTU的估算值。需要注意的是与 IPsec不同,DTLS记录不包含任何连接标识符。应用必须在多个连接之间实现标志多路复用。在使用 UDP时,这个功能很可能由 host/port号来实现。

       多个 DTLS记录可以被放入单个数据报内。它们会被简单地进行连续编码。DTLS记录框架足以确定边界。需要注意的是,数据报负载的第一个字节必须是一个记录的开头。记录可能不会跨越数据报。

3.3、记录负载保护

        DTLSMAC与 TLS1.2的相同。然而,不同于 TLS使用隐式序列号,用于计算 MAC的序列号是 64位的值,是由 epoch和数据出现在线路上的序列号级联而成。需要注意的是DTLSepoch+序列号与 TLS序列号的长度相同。

        TLSMAC计算在协议版本号上是参数化的。对于 DTLS,是线路上的版本,如:对于TLS1.2是{254,253}。需要注意的是 DTLS与 TLSMAC处理方法的一个重要差别是在 TLS中,MAC错误必须导致连接中断。对于 DTLS,接收的实现可以简单地丢弃不想要的记录然后继续维持连接。这个变化可能是由于 DTLS记录并不像 TLS的记录那样彼此依赖。

        通常,DTLS实现上应该默默丢弃 MAC错误或其它部分无效的记录。它可以将错误信息记录成一条日志。如果一个 DTLS实现在收到一个 MAC无效的消息时选择生成一个警报,它必须生成一个 bad_record_macalert。

       DTLS记录包含一个序列号以提供重放保护。序列号的校验应遵循下面的滑动窗口流程。会话的接收报文的计数必须在会话建立时被初始化为 0.对于每个已接收到的记录,接收者必须验证记录中包含的序列号没有与在这个会话的生命周期内接收到的任何其它记录的序列号重复。这应该是在与会话匹配后应用于数据包的第一个检查,以加快对重复记录的拒绝。

       丢弃重复数据通过一个滑动接收窗口来实现(这个窗口如何实现是一个局部的事情,但下面的文字描述了实现上必须展示的功能)。必须支持最小为 32的窗口大小,但大小是 64的窗口更被推荐且应该被设置为默认值。其它的窗口大小(大于最小值)可以由接收者选择。(接收者不能告知发送者窗口的大小)。

       窗口的右边缘代表着当前会话接收到的最高有效序列号的值。序列号比窗口左边缘低的记录会被丢弃。落入窗口范围内的包会依据在窗口中接收到的包的列表进行检查。如果接收到的记录落入窗口中且是新的,或者包处于窗口的右边缘,接收者会进行 MAC验证。如果 MAC验证失败,接收者必须丢弃接收到的非法记录。仅当 MAC验证成功时接收窗口才能更新。

       与 TLS不同,在面对非法记录时 DTLS更容易恢复(例如:非法的格式,长度,MAC等等)。通常,非法的记录应该被静悄悄地丢弃,因此保持了当前连接。然而,一个错误可以被记录日志用于诊断。实现上如果选择了生成一个警报来替代日志,则必须产生 fatal级别的警报以避免攻击者重复探测实现来观察其对各种错误类型的反应。需要注意的是如果DTLS运行在 UDP之上,任何执行这个操作(生成警报)的实现都极易遭受拒绝服务(DoS)攻击,因为伪造 UDP报文十分容易。因此,这个功能并不推荐应用于这样的传输层协议。如果 DTLS是被一个抗伪造的传输层协议(例如,带 SCTP-AUTH的 SCTP)所承载,则发送警报会更安全因为一个攻击者很难伪造一个不会被传输层丢弃的报文。

       为了避免拒绝服务攻击,DTLS采用和 IKE一样的无状态 cookie技术。当客户端发送clienthello消息后,服务器发送 HelloVerifyRequest消息,这个消息包含了无状态 cookie.客户端收到之后必须重传添加上了 cookie的 clienthello消息。

           DTLS中带有 cookie的 ClientHello的结构如下:

struct
{
    ProtocolVersionclient_version;
    Randomrandom;
    SessionIDsession_id;
    opaquecookie<0..32>; //Newfield
    CipherSuitecipher_suites<2..2^16-1>;
    CompressionMethodcompression_methods<1..2^8-1>;
}ClientHello;

       发送第一个 clienthello时,cookie为空,长度为 0,两个 hello的其他值都一样。
HelloVerifyRequest的结构如下:

struct
{
    ProtocolVersionserver_version;
    opaquecookie<0..32>;
}HelloVerifyRequest;

cookie的生成方法:
Cookie=HMAC(Secret,Client-IP,Client-Parameters)
其中,secret为随机数。如果使用了 HelloVerifyRequest机制,在计算 finished的校验消息 MAC值的时候,第一个 clienthello和 helloVerifyRequest不参与计算。

3.4、握手消息格式

struct
{
    HandshakeTypemsg_type;
    uint24length;
    uint16message_seq; //Newfield
    uint24fragment_offset; //Newfield
    uint24fragment_length; //Newfield
    select(HandshakeType)
    {
        case hello_request:HelloRequest;
        case client_hello:ClientHello;
        case hello_verify_request:HelloVerifyRequest;//Newtype
        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;

两边在每次握手时传输的第一个消息 message_seq一直是 0.无论何时每个新消息被生成时,message_seq的值都会加 1。

      DTLS实现维持(至少是想象的)一个 next_receive_seq计数器。这个计数器初始化为0.当接收到一个消息时,如果序列号与 next_receive_seq匹配,则 next_receive_seq递增并且消息被处理。如果序列号小于 next_receive_seq,消息必须被丢弃。如果序列号大于next_receive_seq,实现上应当将这个消息放入队列但可以丢弃。(这是一个简单的空间/带宽折衷)。

       每个 DTLS消息必须在单个传输层报文内。然而,握手消息极可能比最大记录大小大。因此,DTLS提供另一种机制用于将一个握手消息分片为多个记录,每个记录可以被分别传送,从而避免了 IP分片。

       当传输握手消息时,发送者将消息分为 N个连续的数据序列。这些序列不能大于最大握手分片大小且必须共同地包含整个握手消息。这些序列不应重叠。发送者随后产生了 N个握手消息,它们都拥有与原始的握手消息相同的 message_seq。每个新消息都由fragment_offset(前一个分片包含的字节数)和 fragment_length(这个分片的长度)来标识。所有消息的 length域都与原始的消息相同。一个未分片的消息是一种退化的情况:fragment_offst=0且 fragment_length=length。当一个 DTLS实现收到一个握手分片时,它必须缓存它直到它收到整个握手消息。DTLS实现必须能够处理重叠的分片序列。这允许发送者在 PMTU估计值发生变化时使用更小的分片大小重传握手消息。

       需要注意的是与 TLS相同,多个握手消息可以被放置于相同的 DTLS记录中,条件是有空间且它们是一次传送的一部分。因此,有两种方式可以将两个 DTLS消息打包到相同的报文中:在相同的记录中或在分开的记录中。

       虽然定时器的数值是实现上的选择,但定时器的错误处理能导致严重的拥塞问题;例如,如果一个 DTLS的很多实例过早超时并在一条拥塞的链路上过快重传。实现上应该使用 1秒作为定时器初始值(最小值定义于 RFC6298中)每次重传使该值加倍,直到不少于 RFC6298的最大 60秒。需要注意的是我们推荐 1秒的定时器而不是 3秒的 RFC6298默认值以提升对时间敏感应用的延迟性能。由于 DTLS只对握手使用重传而对数据流不使用,对拥塞的影响应该会很小。

       实现应该维持当前定时器的值直到出现没有丢失的传输,这时这个值应当重置为初始值。在经过一个长的空闲周期(不少于 10倍的当前定时器数值)后,实现可以将定时器重置为初始值。可能发生这种情况的一种场景是在重要的数据传输之后产生了一次重握手。

3.5、握手交互消息内容

       DTLS握手阶段如下图:

1)Client_hello报文段

       除了 cookie外,还有客户机产生的 32字节的随机数,其中前 4字节为时间戳,后 28字节为系统产生的随机数。此外,该报文段的内容还有客户机支持的加密方式(PSK或者 ECC)和压缩方式,供服务器进行选择。

      在通过 cookie校验后,服务器发送 server_hello报文段给客户机。该报文段包含有服务器产生的 32字节的随机数,和服务器选中的用来进行之后的会话的加密方式和压缩方式。certificate报文段的内容:在服务器发给客户机的证书报文段中,包含有服务器证书的公钥;客户机接收到该报文段后,按照协议规定,从报文段的对应位置中读取出服务器证书的公钥存入相关变量中。

2)server_key_exchange报文段

       若协议所选加密方式为 ECC(椭圆曲线加密),则在server_key_exchange报文段的构造过程中会使用ECDH(椭圆曲线秘钥交换协议)和ECDSA(椭圆曲线数字签名算法)。ECDH和ECDSA分别是 ECC和 DH(diffie-hellman)秘钥交换协议、DSA(数字签名算法)的结合。

       在 server_key_exchange报文段中,包含有所选用的椭圆曲线 E,阶 N和基点 G的 x,y坐标,客户机在收到这个报文段后,进行对应的格式检验,并读取数据,因此服务器和客户机共同获得约定好的用来进行 ECDH秘钥协商交换协议的参数,从而可以共同协商出相同的对话秘钥用于加密之后的会话内容。

       同时,为了防范中间人攻击,服务器还在 server_key_exchange报文段的末尾对整个报文段进行了 ECDSA数字签名。具体签名过程为先用 client_hello报文段和 server_hello报文段中的 2个 32字节的随机数作为函数参数,利用 sha256哈希算法对 server_key_exchange报文段本身的载荷产生摘要,然后再用服务器的私钥和 sha256哈希算法进行 ECDSA数字签名,得到签名结果 r和 s,并写入 server_key_exchange报文段的末尾。

       客户机在收到 server_key_exchange报文段后,先进行各数值项格式的校验,然后提取出报文段末尾的签名值 r和 s。之后,用已经读取出的服务器的公钥的 x,y坐标值来server_key_exchange报文段进行 ECDSA签名验证,若结果和报文段中的 r和 s值一致,则报文段通过验证。

       整个 DTLS协议的加密方式可选用 ECC或 PSK(预共享秘钥,PreSharedKey)两种。若为 ECC,则通过 ECDH协议来进行通信双方的秘钥协商;若为 PSK,则直接以通信双方事先就已经约定好了的秘钥为基础来进行加密通信。

       对于 PSK加密通信来说,验证对方的通信身份非常关键。所以通信双方会在本地存取对方的 psk_id(即身份标志)和 psk_id_length(身份标志长度),通过比较收到的报文段中的psk_id,psk_id_length和本地存储的是否完全一致来进行对方身份的验证。

       在整个通信过程中,采用 PSK与 ECC的区别主要体现在 server_key_exchange报文段、
client_key_exchange报文段的内容不同和双方计算得到预主秘钥方式的不同。当采用 PSK加密时,server_key_exchange报文段和 client_key_exchange报文段的内容分别是服务器与客户机各自的 psk_id和 psk_id_length,由此双方可以互相知道对方的psk_id和 psk_id_length。

      之后,双方都会对收到的报文段进行检验,只有 psk_id和 psk_id_length与本地存储的
完全一致才会进行后面的通信。当双方都通过身份验证后,双方再各自用相同的函数产生预主秘钥,而函数的参数包括之前通信阶段中双方各自产生的 32字节的随机数,由此可以保证虽然本地存储的 psk秘钥不变,但每次临时通信时的会话秘钥还是会一直变化的,从而增强了抗攻击性。双方产生预主秘钥后,再调用和使用 ECC加密的相同方式来产生主秘钥,即用于之后会话通信的对称秘钥,该过程中依然会用到双方产生的 32字节的随机数。

3)server_hello_done报文段和 client_key_exchange报文段的内容

      服务器发送的server_hello_done报文段的载荷部分为空,只是发给客户机来作为标志,表示服务器当前阶段的报文段已经发送完毕。

       客户机在收到 server_hello_done报文段后,发送 client_key_exchange报文段给服务器,里面包含了用于秘钥协商的基点的 x,y坐标,并且不同于 server_key_exchange报文段,客户机 并 没 有 在 报 文 段 的 末 尾 进 行 ECDSA 数 字 签 名 。 之 后 , 客 户 机 再 通 ecdh_pre_master_secret函数来产生用于之后会话的预主秘钥。其中函数的参数包括客户机自己的私钥,和服务器共享的用于 ECDH秘钥协商算法的基点的 x,y坐标。产生预主秘钥后,再根据之前阶段客户机和服务器分别产生的 32字节的随机数产生主秘钥 master_secret,此时主秘钥为对称秘钥,用于之后会话的加解密。

4)change_cipher_spec报文段和 finished报文段的内容:

       客户机计算出会话秘钥后,发送change_cipher_spec报文段给服务器,这个报文段的有效载荷为空,用来作为标志通知服务器,表示客户机已经算出主秘钥,之后发送的报文段会采用主秘钥加密。

       握手阶段中客户机发送的最后一个报文段为 finished报文段,载荷内容为 MAC值(消息验证码),用于给服务器做认证。并且值得注意的是,finished报文段作为记录层的载荷部分在发送时已经用上一步产生的会话秘钥进行加密编码。服务器在收到客户机发送过来的finished报文段后,也会和客户机用 ECDH秘钥协商算法经过相同的流程,调用相同的函数先产生预主秘钥,再产生主秘钥。最后,服务器产生经会话秘钥加密后的 finished报文段给客户机,标志整个握手阶段的结束。
       客户机收到服务器发过来的 finished报文段后,便可发送应用数据。并且应用数据会一直用会话秘钥加密,从而实现了 UDP所不具备的安全性。

猜你喜欢

转载自blog.csdn.net/chenlycly/article/details/126496744
今日推荐