前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >安全的数据库图形管理工具(2):三个问题

安全的数据库图形管理工具(2):三个问题

作者头像
不可言诉的深渊
发布2019-07-26 17:11:11
5930
发布2019-07-26 17:11:11
举报

上次虽然实现了加密传输,也通过了简单的测试,但是我在进一步测试时发现了一些问题,下面我就来从根本上解决这些问题,在解决这些问题之前,首先附上之前文章的链接。

安全的数据库图形管理工具(1):准备密钥

加密长字节序列

之前我只是用两个短字节序列来进行密钥测试,那两个字节序列都比较短,可是我在进行进一步测试的时候发现长字节序列无法被加密,不相信的话我可以尝试一下。

为了进行简单的测试,我就把客户端代码要发送的字节改成特别长的而已。

代码语言:javascript
复制
import rsa
import socket
public_key = open("server_public_key.pem", "rb").read()  # 打开公钥文件并读取
public_key = rsa.PublicKey.load_pkcs1(public_key)  # 加载公钥
private_key = open("self_private_key.pem", "rb").read()  # 打开私钥文件并读取
private_key = rsa.PrivateKey.load_pkcs1(private_key)  # 加载私钥
# 用公钥加密要发送的数据
send_encode_data = rsa.encrypt(b"123456789012345678901234567890", public_key)
s = socket.socket()  # 创建套接字对象
host = "111.230.108.44"  # 服务器的IP地址
port = 1234  # 服务器程序的端口号
s.connect((host, port))  # 连接服务器
s.send(send_encode_data)  # 发送已经加密的数据
receive_encode_data = s.recv(128)  # 接受已经加密的数据
print(rsa.decrypt(receive_encode_data, private_key).decode())  # 解密接收到的加密数据并输出

要加密的节已经够长了,下面我们来看看运行情况。

运行之后发现出问题了,稍微翻译一下出错信息:消息需要30个字节,但是只有21个字节的空间。我们首先来想一个问题,为什么一次只能加密21个字节?21从何而来?

我直接给出结论吧,可以被加密的字节长度与密钥的比特数呈线性正相关,我们有如下公式:

我上次设置的密钥比特数是256,最大长度也就是256/8-11=21。21就是这么来的,超过了这个长度就会出现问题。如何解决这样的问题其实很简单,密钥比特数设置一个很大的数就行了。但是这样治标不治本,万一加密的数据比那个很大的数还要长怎么办?还是很简单,我把这一个长字节序列分成一块一块的,每一块20个字节,在解密的时候,我们也一块一块的解密,然后拼接起来就行。

代码语言:javascript
复制
for i in range(0, len(cmd), 20):
    sock.send(rsa.encrypt(cmd[i:i + 20], public_key))

其中cmd是要加密的字节序列,sock是一个套接字对象,这就是一个先加密后发送的过程,有些人会有一个问题,发送过去一定要让对方接收吧,不可能只发送不接收,既然发送需要分成一块一块的,我接收也应该是一块一块的,发送20个长度的字节序列,接收应该也是接收20个长度的字节序列啊?!如果真的是这样,那么最后一块该如何接收?因为最后一块几乎不可能是20个字节长度,比如我有45个字节序列需要发送,两个20发完之后最后发一个5个字节的块。就在这个时候,我必须要求接收缓冲区只能接5个字节,如果多了就会出现问题。因为接收缓冲区如果依旧是用20个字节从接收缓冲区读取数据,就会出现这样一种情况,接收到的数据也是20个字节,前5个是最后一次发送的数据,后15个是第二次发送的20个字节的后15个字节。如何解决这个问题将在后面讨论,因为现在即使解决了这个问题,接收方解密依旧还是有问题。RSA加密算法规定,只要长度在合法的范围内,我们有如下公式:

通过上面的公式我们可以看出在其他条件不变的情况下,密文长度与明文长度无关,不管明文多长,密文的字节长度固定不变,在我这里就是256/8=32,所以我要求接收方每次接收32个字节长度。

TCP粘包

在上面我稍微提到了一个问题,假设我有45个字节序列需要发送,两个20发完之后最后发一个5个字节的块。就在这个时候,我必须要求接收缓冲区只能接5个字节,如果多了就会出现问题。因为接收缓冲区如果依旧是用20个字节从接收缓冲区读取数据,就会出现这样一种情况,接收到的数据也是20个字节,前5个是最后一次发送的数据,后15个是第二次发送的20个字节的后15个字节,我们称这种情况叫粘包。下面我来重点解决这个问题,为什么会出现粘包?因为发送和接收都太快了,导致缓冲区没有刷新,最简单的办法我们就是使用sleep给缓冲区一个刷新的时间,但这样做性能太差了,我们暂时先想一下有没有更好的办法,如果我们规定发送多少个字节就接收多少个字节,这样就可以获得一个平衡,从而不会出现接收到多余的无用的数据。现在最关键的问题出来了,我怎么把发送要发送的字节长度告诉接收方?接收方又该如何接收?接收多少个字节?如果我就简单的把长度这个整数使用str转换成字符串,然后编码成字节,这个字节的长度是不确定的,接收方设置接收字节数就陷入了麻烦,如何把长度给固定住?为此我们可以使用模块struct,struct可以把一个整数压缩成四个字节,现在又出现了一个问题,4个字节存放的整数有范围,万一越界怎么办?很简单,我再做一层封装,先创建一个报头,再把报头转成字节,然后把字节报头的长度用struct压缩打包发过去就行了。

缓冲区溢出

在网络编程中,如果服务器发送速度和客户端接收速度不匹配,假设服务器发送太快,客户端接收的有点慢,默认情况下服务器并不会配合客户端的接收速度,而是会一股脑的把数据丢在缓冲区,分块发送按理来说没毛病,但是如果不给服务器刷新缓冲区的机会,依旧会造成溢出。在python网络编程中,我一时半伙找不到清理套接字缓冲区的办法,只能sleep将就了。

一个简单的SSH远程控制终端

下面我通过编写一个简单的SSH远程控制终端来进行进一步测试,首先说一下设计思路。我们要求客户端输入命令发送过去,服务器返回命令执行结果给客户端,数据传输一律是非对称加密。下面我详细的说一下客户端程序与服务器程序的设计细节。

客户端

客户端的实现非常简单,首先读取自己的私钥和服务器的公钥并赋值给两个变量。然后连接服务器,连好之后就是开始输入命令,输入完成之后就将命令分块加密发送,发送完成之后就接收对方响应过来的报头长度,然后接收报头,之后就开始接收真实数据,然后把接收的数据解密即可。具体细节我就不讲了,直接给出源代码。

代码语言:javascript
复制
import socket
import rsa
import json
import struct
public_key = rsa.PublicKey.load_pkcs1(open("server_public_key.pem", "rb").read())  # 加载公钥
private_key = rsa.PrivateKey.load_pkcs1(open("self_private_key.pem", "rb").read())  # 加载私钥
sock = socket.socket()  # 创建套接字对象
sock.connect(('', 8080))  # 连接服务器
while True:
    cmd = input().strip().encode()  # 1.输入命令 2.去除无效字符 3.编码成字节序列
    if not cmd:  # 如果输入的命令为空,继续下一次循环
        continue
    elif cmd == b'logout':  # 如果命令是logout就结束循环
        break
    for i in range(0, len(cmd), 20):  # 分块加密,一块20个字节
        sock.send(rsa.encrypt(cmd[i:i + 20], public_key))  # 发送加密的数据
    head = sock.recv(4)  # 接收报头长度
    head_json = struct.unpack("i", head)[0]  # 获取报头长度
    head_dic = json.loads(sock.recv(head_json).decode())  # 1.接收报头 2.将接收的报头解码成字符串 3.将字符串转换成对应的字典
    data_size = head_dic["data_size"]  # 获取字典的value,也就是真实数据长度
    block_list = []  # 接收数据的容器
    recv_size = 0  # 接收到的数据长度
    while recv_size < data_size:  # 当实际接收的数据长度小于应该接收的数据长度,就继续接收
        block = sock.recv(32)  # 接收数据,一次32个字节
        recv_size += len(block)  # 改变实际接收的数据长度
        block_list.append(block)  # 将接收的数据添加到容器中
    if data_size != 0:  # 如果应该接收的数据长度不等于0
        if block_list[-1] == b'':  # 如果最后一块是空字节
            del block_list[-1]  # 将最后一块删去
    for i in range(len(block_list)):  # 分块解密
        block_list[i] = rsa.decrypt(block_list[i], private_key)
    response = b"".join(block_list).decode()  # 拼接容器中的数据并解码成字符串
    print(response)  # 输出这个字符串
sock.close()  # 程序结束之前,关闭套接字对象

服务器

服务器的实现也非常简单,基本上和客户端差不了多少,就是多了一个处理数据的过程,处理数据非常简单,就是执行命令并获取命令结果,执行命令可以调用os模块中的system函数,当然有更好的办法,我是直接怎么简单怎么来。至于如何获取命令执行结果我也是用最简单的方法了。命令执行有两种结果,正确和错误,正确的结果在标准输出流stdout中,错误的输出结果在标准出错流stderr中,我们直接对输出重定向,将结果直接写入文件。然后就是读取文件,发送数据。下面具体的细节也不讲了,直接给出源代码。

代码语言:javascript
复制
import socket
import rsa
from os import system
import json
import struct
from time import sleep
sock = socket.socket()  # 创建套接字对象
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 设置套接字选项
sock.bind(('', 8080))  # 将IP和端口捆绑
sock.listen(1)  # 设置最大连接数
public_key = rsa.PublicKey.load_pkcs1(open("client_public_key.pem", "rb").read())  # 加载公钥
private_key = rsa.PrivateKey.load_pkcs1(open("self_private_key.pem", "rb").read())  # 加载私钥
conn, addr = sock.accept()  # 接受客户端连接
while True:
    try:
        conn.setblocking(True)  # 设置阻塞,防止后面的超时一直生效
        block = conn.recv(32)  # 接收数据
        block_list = []  # 接收数据的容器
        conn.settimeout(1)  # 设置超时,防止第23行发生阻塞
        while len(block) == 32:  # 当接收的字节等于32个,就一直接收
            block_list.append(block)  # 将接收的数据添加到容器中
            try:  # 尝试继续接收
                block = conn.recv(32)
            except socket.timeout:  # 如果超时,就把接收的数据置空,因为没有数据到来
                block = b""
        conn.setblocking(True)  # 设置阻塞,防止前面的超时一直生效
        block_list.append(block)  # 最后一次添加到容器
        if block_list[-1] == b'':  # 如果最后一个是空字节,就删去
            del block_list[-1]
        for i in range(len(block_list)):  # 分块解密
            block_list[i] = rsa.decrypt(block_list[i], private_key)
        request = b"".join(block_list).decode()  # 拼接容器中的数据并解码成字符串
        system(request+" 1> out 2> err")  # 执行命令(在命令执行过程中已经重定向到文件了)
        out, err = open("out", "rb").read(), open("err", "rb").read()  # 读取文件中的内容
        err_list = []  # 出错列表
        out_list = []  # 输出列表
        for i in range(0, len(err), 20):  # 如果当前的块不为空,将加密之后添加到出错列表中
            if err[i:i+20] != b"":
                err_list.append(rsa.encrypt(err[i:i+20], public_key))
            else:
                break
        for i in range(0, len(out), 20):  # 如果当前的块不为空,将加密之后添加到输出列表中
            if out[i:i+20] != b"":
                out_list.append(rsa.encrypt(out[i:i+20], public_key))
            else:
                break
        err = b"".join(err_list)  # 拼接加密之后的错误数据
        out = b"".join(out_list)  # 拼接加密之后的正确数据
        response_head = json.dumps({"data_size": len(out)+len(err)}).encode()  # 设置报头并转换成对应的类型
        conn.send(struct.pack("i", len(response_head)))  # 将报头长度压缩成一个定长字节序列并发送
        conn.send(response_head)  # 发送报头
        for i in range(0, len(err), 32):  # 分块发送出错的数据
            if len(err[i:i+32]) != 0:
                conn.send(err[i:i+32])
                sleep(0.001)  # 防止因为发送太快发送缓冲区溢出
        for i in range(0, len(out), 32):  # 分块发送正确的数据
            if len(out[i:i+32]) != 0:
                conn.send(out[i:i+32])
                sleep(0.001)  # 防止因为发送太快发送缓冲区溢出
    except KeyboardInterrupt:
        break
    except ConnectionResetError:
        break
sock.close()  # 在程序结束之前,关闭套接字对象

测试

下面再稍微的做一些测试看看有没有问题,运行这个程序非常简单,先服务器再客户端,然后在客户端控制台中输入命令,等待结果返回就行,运行结果如图所示。

通过结果我们可以看出,服务器能够正常执行命令,客户端也同样可以接受到命令的结果。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 加密长字节序列
  • TCP粘包
  • 缓冲区溢出
  • 一个简单的SSH远程控制终端
    • 客户端
      • 服务器
        • 测试
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档