网安技术与应用(2)——基于DTLS的安全服务器设计

一 实验目的

通过实验,掌握DTLS的基本原理,掌握python3-dtls库的基本使用。

二 实验内容

  1. 利用 DTLS 库编写客户端和服务器程序,服务端开启监听,提供数据传输、文件传输功能;
  2. 客户端对服务端进行证书认证(单向认证);
  3. 利用相关工具(如 Wireshark)验证 DTLS 通信结果。

如果下载速度较慢可以考虑更换下载源

Ubuntu20.04更换下载源

三 实验原理

1、DTLS和TLS

DTLS(Datagram Transport Level Security)即数据报安全传输协议,被广泛应用于保护网页流量和其他应用协议。

TLS 提供了一个透明的有向通道,通过在应用层和传输层之间插入 TLS 来保护应用层协议(如 HTTP等)。然而 TLS 必须依赖可靠的传输通道,如 TCP,因此 TLS 无法用于保护 UDP 的数据包流量。

简单来说,DTLS 可以理解为 UDP 上的 TLS 协议,它为 UDP 报文提供了安全传输能力。由于 UDP 协议是不可靠传输协议,其不对丢包、排序等负责,而 TLS 内部也不具备处理这种不可靠性的机制,因此 TLS 无法直接应用于 UDP 上。

2、握手过程

对于传输层安全来说,密钥交换机制和数据加密及签名算法决定了整个方案的安全等级。而密钥协商都必须通过握手流程完成,因而这是理解DTLS的关键要点。

其流程与TLS概念上是一致的,其中:

  • Flight:对应一次通过网络发送的数据包;
  • HelloVerifyRequest:用于服务端对客户端实现二次校验;
  • Certificate:交换的证书,由协商后的算法确定是否需要传输;当服务端要求验证客户端身份时,发起CertificateRequest,此时客户端需要发送证书;
  • ChangeCipherSpec:一个简单的标记,标明当前已经完成密钥协商,可以准备传输;
  • Finished:表示握手结束,通常会携带加密数据由对端进行初次验证。

3、CipherSuite

由于网络 IO 限制,DTLS 只支持 TLS 的子集。

  • ECDHE_RSA:密钥交换算法,这是由 ECC 和 DH 密钥交换算法衍生出来的算法;
  • AES_128_GCM:动态密钥算法,用于实现数据包的加解密;
  • HMAC_SHA256:MAC 算 ,用于创建加密数据块的摘要;
  • PRF:伪随机函数,TLS1.2 定义其与 MAC 算法一致。

几个常用的 CipherSuite:

  • l TLS_PSK_WITH_AES_128_CBC_SHA256
  • l TLS_PSK_WITH_AES_128_CCM_8
  • l TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • l TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8

4、防护机制

(1)握手

  • 重传

DTLS 使用超时重传机制来确保握手消息的到达。

  • 顺序

UDP 本身不对消息传输的顺序负责,为了保证握手消息按序传输,每一个 Handshake 报文都分配了一个特殊的序列号,接收方直接处理属于当前步骤的消息,对于提前到达的消息则放到缓存队列中。

  • 分段

由于 MTU 限制,DTLS 要求对握手消息实现分段,每一个握手消息都可能包含分段的位移和长度,由接收端组装。

  • 重复

DTLS 选择性的支持消息重放检测机制,使用的技术与 IPsec AH/ESP 相同,由接收方维护一个 bitmap 窗口,因老化而不适合窗口的记录和以前接收过的记录都会被悄悄地丢弃。

(2)数据包传输

DTLS 为每个加密数据包增加了 MAC 鉴权摘要,用于保证数据包的完整性;此外显式附带了一个 SN 号用于排序。

(3)DoS 攻击

DTLS 定义了基于 cookie 验证的机制来预防攻击,如前面流程中涉及的 HelloVerifyRequest 便是用于进行 cookie 验证。Cookie 的算法:HMAC(Secret, Client-IP, Client-Parameters) 。

  • Secret:由 server 端内置,用于计算 cookie 值;
  • Client:需要在接收到 VerifyRequest 后提供同样的 cookie 值;
  • server:根据发送方 IP 计算 cookie 值,一旦发现不一致则判定为非法数据。

(4)会话恢复

握手流程所占的开销是比较大的,与 TLS 类似,DTLS 也定义了会话恢复机制。

握手成功之后,服务端将生成 SessionID 返回,客户端在下次连接时附带 SessionID;若验证通过,可直接沿用原有的会话数据,包括协商算法和密钥。

四 实验条件

  1. 操作系统:Ubuntu 20.04(其他 Linux 发行版均可,满足内核≥4.8);
  2. 工具软件:Python 3,Wireshark;

五 实验过程

完整代码可以到https://download.csdn.net/download/Netceor/87473061中下载

1、安装python-dtls和wireshark

  • 安装python3-dtls

python3-dtls 是 Python 3版本的 DTLS 实现,其对 Python 原生的 ssl 库进行 patch,使其得以支持 DTLS。执行如下命令以安装:

pip install python3-dtls
  • 安装wireshark
sudo add-apt-repository universe
sudo apt install wireshark

运行命令后会出现弹窗,选择“是”

image-20220330144813044

2、启动wireshark

先将wireshark启动,用于捕获后续的Server和Client通信过程

sudo wireshark  # 运行完将弹出一个窗口

3、Server和Client细节

(1)Server端

  • 建立通信

首先是利用socket模块,建立通信。利用socket通信绑定相关IP地址和端口。

current_path = path.abspath(path.dirname(__file__))
cert_path = path.join(current_path, "certs")
sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sck.bind(("127.0.0.1", 28000))
sck.settimeout(30)
  • 封装函数SSL

接着是利用其自带的封装函数SSLConnection,这个函数会将SOCKET通信封装到DTLS上去。我们只需要在里面添加证书和密钥参数即可。

scn = SSLConnection(
    sck,
    keyfile=path.join(cert_path, "keycert_ec.pem"),
    certfile=path.join(cert_path, "keycert_ec.pem"),
    server_side=True,
    ca_certs=path.join(cert_path, "ca-cert_ec.pem"),
    do_handshake_on_connect=False)
  • 等待响应

接着是等待client响应,首先是利用listen的技术,来找到连接的client端的信息,包括有IP地址以及他开放的端口。这里设计一个while循环进行循环监听。一有client他就会实现连接。

cnt = 0
while True:
    cnt += 1
    print("Listen invocation: %d" % cnt)
    peer_address = scn.listen()
    if peer_address:
        print("Completed listening for peer: %s" % str(peer_address))
        break

print("Accepting...")
conn = scn.accept()[0]
sck.settimeout(5)
conn.get_socket(True).settimeout(5)
  • 实现握手

接着是实现握手,这里只需要使用到他封装好的函数do_handshake()函数即可,具体的我们会在后续抓包的过程中进行分析

cnt = 0
while True:
    cnt += 1
    print("Listen invocation: %d" % cnt)
    peer_address = scn.listen()
    assert not peer_address
    print("Handshake invocation: %d" % cnt)
    try:
    	conn.do_handshake()
    except SSLError as err:
        if err.errno == 504:
        	continue
    	raise
    print("Completed handshaking with peer")
    break
  • 传输应用数据

这里读取数据采用的是SOCKET通信中的数据传输,利用封装好的read()函数和write()函数,对数据进行传递

cnt = 0
while True:
    cnt += 1
    # print("Listen invocation: %d" % cnt)
    # peer_address = scn.listen()
    # assert not peer_address
    # print("Read invocation: %d" % cnt)
    try:
        message = conn.read()
    except SSLError as err:
        if err.errno == 502:
            continue
        if err.args[0] == SSL_ERROR_ZERO_RETURN:
            break
        raise
  • ls和get

    这其中包括了一些功能,包括有远程命令的执行,远程文件传输。

    这里主要实现了两个功能,ls功能和get功能,分别是列举出当前目录下的文件以及远程传输文件。具体技术细节如下:

    利用传输命令中存在空格,因此利用空格区分cmd和文件,判断cmd为ls还是get功能,如果是需要命令执行的话,就利用subprocess函数封装的命令执行,接着将生成的结果以write的形式传输给client。

    data = message.decode()
    print("from client:%s"%data)
    cmd_filename = data.split(' ')
    if cmd_filename[0] != "ls" and cmd_filename[0] != "get":
        conn.write("please input True cmd")
        continue
    
    • ls

      如果是ls函数命令的话,会列举出当前目录下的文件,将生成的结果以write的形式传输给client

      if cmd_filename[0] == "ls":
          obj = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE)
          cmd_result = obj.stdout.read()
          conn.write(cmd_result)
      
    • get

      如果是get函数命令的话,那么就获取到他想要读取的文件的路径,这里支持./路径,根目录路径等。通过判断,整合路径,从而获取到文件在哪里,然后利用读取文件的操作。将文件的内容读出来(可以以二进制的形式读出来,这样比较好传输,但是这里我用文本的形式读出来,方便client获取到读完的信号。)

      同时设置blocksize为1024B,这样读取数据的时候可以分块进行读取。并使用指针和while循环进行连续读取,直到读完

      elif cmd_filename[0] == "get":
          filename = cmd_filename[1]
          if filename[0] == "/":
              filedir = filename
          else:
              if filename[:2] == "./":
                  filename = filename[2:-1]
              filedir = path.join(current_path,filename)
          with open(filedir,"r") as fd:
              while True:
                  byte = fd.read(blocksize)
                  if not byte:
                      conn.write("Already Send".encode())
                      break
                  conn.write(byte.encode())
      
  • 解除连接

数据传输完毕之后就可以结束了,利用unwrap()函数解除连接

cnt = 0
while True:
    cnt += 1
    print("Listen invocation: %d" % cnt)
    peer_address = scn.listen()
    assert not peer_address
    print("Shutdown invocation: %d" % cnt)
    try:
        s = conn.unwrap()
        s.close()
    except SSLError as err:
        if err.errno == 502:
            continue
        raise
    break

以上为Server端的代码,完整代码见附件。

(2)Client端

  • 绑定dtls函数到socket

这里利用到的是ssl中wrap_socket函数,将dtls函数绑定到socket上,利用do_patch()函数进行绑定。

由于是服务器端单向的认证,因此不需要做过多的认证。

do_patch()

blocksize = 1024
def main():
    cert_path = path.join(path.abspath(path.dirname(__file__)), "certs")
    s = ssl.wrap_socket(socket(AF_INET, SOCK_DGRAM), cert_reqs=ssl.CERT_NONE,
                        ca_certs=path.join(cert_path, "ca-cert_ec.pem"))
    s.connect(('127.0.0.1', 28000))
  • 截取输入命令

接着就是对输入命令的截取,根据不同的情况实现不同的功能,同时在传输文件这个功能上进行区别化对待,写一个while循环将文件内容传输过来

try:
    while True:
        print("input ls <dir> to list files in dir.\n")
        print("input get <filename> to get files from dir.\n")
        send_msg = input(">")
        cmd, filename = send_msg.split(" ")
        try:
            s.send(send_msg.encode())
        except Exception as e:
            print("[-]Can not send Data")
        try:
            if cmd == "ls":
                data = s.recv(blocksize)
                print(data.decode())
            else:
                filename = filename.split("/")[-1]
                filedir = "./"+filename
                with open(filedir,"wb") as fd :
                    while True:
                        data = s.recv(blocksize)
                        if data.decode() == "Already Send":
                            print("Already Reveive.")
                            break
                        fd.write(data)

        except Exception as e:
            print("[-]Can not receive Data")
except KeyboardInterrupt:
    s.close()
    sys.exit(0)

以上为Client端的代码,完整代码见附件。

4、连接和握手测试

(1)启动Server和Client

服务端接受客户端连接,并输出握手消息:

image-20220331145140721

可以发现先是服务器等待客户端接入,在客户端接入后,服务器成功听到并与其握手,等待客户端的请求。

(2)文件查看

  • Client

image-20220331143802315

  • Server

image-20220331143824913

(3)文件下载

Server下存在有两个文件,server.py和1.txt,我们在Client利用get指令获取一下

  • Client

image-20220331143952154

查看文件目录,发现已经获取到了两个文件

image-20220331144321672

  • Server

Server端也收到指令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9vBXB5o-1651129190843)(https://s2.loli.net/2022/03/31/4D7SqXzcyI3tabJ.png)]

查看文件夹下有该文件

image-20220425201944293

5、Wireshark分析DTLS协议握手及通信过程

分析内容见六 思考题

image-20220331145507076

(1)抓取到Hello报文

握手的第一步即ClientHello,需要关注的是Secure Sockets Layer,即安全套接字层。

image-20220331144825691

image-20220331144830541

(2)抓取到Cookie

image-20220331145609969

(3)抓取到密钥协商过程

image-20220331145544036

image-20220331145655457

image-20220331145702028

(4)抓取到加密通信的内容

image-20220331150204707

image-20220331150219143

该部分实验完成。

六 思考题

  • 每个SSL消息的消息头会有三个域(值可能不同)。其中一个域是“内容类型”(content type,8 bits)。请列出所有三个域,以及它们的长度?

    Content Type字段为1字节,Version字段为2字节,Length字段为2字节:

    image-20220425222035641

  • 捕获Client Hello消息,请问这个Client Hello的“内容类型”(content type)是什么?

    HandShake,表示内容类型为握手消息。

    image-20220425221840905

  • Client Hello消息包含“challenge”字段吗?如果包含,那么“challenge”值的十六进值是多少?

    包含,即下图中的Random字段。

    image-20220425221803302

  • Client Hello消息中包含客户端支持的加密方式了吗?如果包含,请列出。请简介什么是公钥算法,对称加密算法,哈希算法。

    包含:①非对称加密(公共密钥):ECDHE_ECDSA ②对称加密(共享密钥):AES_128 ③哈希(散列)加密:SHA256

    ① 公钥算法:使用两个不同的密钥,两个密钥无法不一样,无法相互推出,但是可以相互解密,公钥指的是可以任意发布的密钥,私钥是指只有自己持有,不给任何人使用(自己唯一所以才叫私钥),包括已经信任的另一方。特例,当公钥私钥相同变成非对称加密。

    ② 对称加密算法:加密和解密使用相同密钥

    ③ 哈希算法:一种基于Hash函数的文件构造方法,可实现对记录的快速随机存取。它把给定的任意长关键宇映射为一个固定长度的哈希值,一般用于鉴权、认证、加密、索引等。其主要优点是运算简单,预处理时间较短,内存消耗低,匹配查找速度比较快,便于维护和刷新,支持匹配规则数多等。

  • Server Hello SSL消息包含“challenge”字段吗?如果是,那么长度是多少?客户端和服务端使用“challenge”字段目的是什么?

    包含,有32字节,也就是用64个16进制数来表示。
    多次随机数生成为未来生成对话密钥提高安全性能。即下图中的Random字段。

    image-20220425221803302

  • Server Hello SSL消息中包含会话ID吗?建立会话ID的目的是什么?

    包含(有的可能不会包含,也就是可以有也可以没有)
    目的:用一定时间内端口连接快速恢复连接过程。

  • 这个消息中包含证书吗?证书是否符合一个单独的以太网帧的格式?

    此记录不包含证书,但是可以看到后面的一个Server Hello是包含证书的,而且包含在单独的记录内。
    适合在一个单独的以太网帧传输。

  • 找到Client Key Exchange消息,这个消息包含主密钥吗?这个密钥用来干什么?这个密钥被加密了吗?如果是,那么加密后的字段长度为多少?

    包含。
    使用EC Diffie-Hellman(ECDH加密算法)进行加密传输,使用的是服务器公钥加密,用以给服务器让服务器用私钥解密并且使用前面两个hello过程的随机数生成本次的会话加密密钥。
    32个字节。

    image-20220425223136055

  • 发送Change Cipher Spec消息和Encrypted Handshake消息的目的是什么?

    1、通知对端改变当前使用的加密方式,change cipher spec 实际可用于通知对端改版当前使用的加密通信方式。

    2、告诉对端自己在整个握手中收到了什么数据,发送了什么数据,保证中间没人篡改报文。首先,无论是客户端还是服务端,都会在握手完成之后,发送 Encrypted handshake message,且各自收到对端的Encrypted handshake message后会去验证这个数据。 具体 这个 Encrypted handshake message 怎么计算,就是把当前(准备发送Encrypted handshake message)前,自己收到的数据和发送的数据进行一次简单运算(hash+加密)。如果中间有人篡改了报文,比如,把客户端的client hello中的提供的加密套件改成了 一个弱秘钥算法,那么对于server而言,收到的client hello 和 客户端实际发送的是不同的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值