CMDB 数据加密 最终整合API验证+AES数据加密

当CMDB运行在内网的时候,经过API验证的三关是没有问题的,但是如果运行在外网,有一个问题是,黑客截取后的访问速度比客户端快的时候还会造成数据泄露。为了解决这个问题,就要对数据进行加密

RSA加密

RSA是一种非对称加密,但是对加密的字符串的长度需要设置。但是字符串的长度是不固定的,而且这种方式的加密,加密的字符串越长,加密的时间越长。

  • pip install rsa
  • rsa.newkeys(256) 256代表加密的位数 256/8=32 就是32个字节,但是源码中32-11=21 也就是最多加密21个字节
import rsa
import base64

# ######### 1. 生成公钥私钥 #########
pub_key_obj, priv_key_obj = rsa.newkeys(256)

pub_key_str = pub_key_obj.save_pkcs1()
pub_key_code = base64.standard_b64encode(pub_key_str)

priv_key_str = priv_key_obj.save_pkcs1()
priv_key_code = base64.standard_b64encode(priv_key_str)

print(pub_key_code)
print(priv_key_code)


# ######### 2. 加密 #########
def encrypt(value):
    key_str = base64.standard_b64decode(pub_key_code)
    pk = rsa.PublicKey.load_pkcs1(key_str)
    val = rsa.encrypt(value.encode('utf-8'), pk)
    return val


# ######### 3. 解密 #########
def decrypt(value):
    key_str = base64.standard_b64decode(priv_key_code)
    pk = rsa.PrivateKey.load_pkcs1(key_str)
    val = rsa.decrypt(value, pk)
    return val


# ######### 基本使用 #########
if __name__ == '__main__':
    v = 'Django'
    v1 = encrypt(v)
    print(v1)
    v2 = decrypt(v1)
    print(v2.decode('utf8'))

AES 加密

微信公众号的加密方式,而且对字符串的长度没有限制

但是要加密的字符串必须是16个字节或者是16个字节的倍数

安装模块

由于用到了Crypto,通过pip安装后Python3.6中仍然不能运行。自己是用的Anconda中的自带的,可以使用。
在GitHub找到了已经编译好的Python3.5的pycrypto.

加密过程

  • 加密用到的key 必须是16个字节或16字节的倍数
  • 被加密的字符串同样也必须是16个字节或16字节的倍数
  • 通过bytearray能对字节的长度进行修改
  • 用空格补足的算法 对bytearray用16取余,16-余数+原来的长度就补到了32
# ############## 加密 ##############
from Crypto.Cipher import AES

key = b'jlaksdflj77asdfh'  # 16个字节或16字节的倍数
cipher = AES.new(key, AES.MODE_CBC, key)

data = '要加密的字符串'  # 一个中文是3个字节
byte_data = bytearray(data, encoding='utf8')  # 想要动态修改字节,用bytearray 相当于把字节转换成一个数组
# print(len(byte_data))
v1 = len(byte_data)  # 这是要加密的数据的长度 21
v2 = v1 % 16  # 取余 5
v3 = 16 - v2  # 这是要补足的数 11 : 21+11=32 是16的倍数
for i in range(v3):
    byte_data.append(32)  # 32代表ASCII中的空格 chr(32) 可以查看
# print(len(byte_data))
final_data = byte_data.decode('utf8') # 把字节解码成字符串
print(final_data) # 加密的内容并加上了补足的空格
msg = cipher.encrypt(final_data)  # 进行加密  final_data必须是16个字节或16字节的倍数

print(msg) # 加密后是字节

解密过程

  • 下面是直接利用了加密后的字节msg
  • 解密后删除空格
# ############## 解密 ##############

msg = b'G\xf47P\xf4X\xbf\x88\xba^;\xbcA\xde(\xb0)\xafA\xb8\xd8\t\xca\xef\xd7\xcdZhw\x98?N'
key = b'jlaksdflj77asdfh' # 需要同样的key
cipher_jm = AES.new(key, AES.MODE_CBC, key)
result = cipher_jm.decrypt(msg) # 对字节进行解密
print(result.decode('utf8').strip()) # 解密后删除空格

不用空格进行补足--机智的加密解密

如果原来要加密的字符串中存在空格,机密后会删除空格,这是一个问题

机智的加密方式

  • 当需要补几个字节的时候,就补上几个。如需要11个字节,就补11个11(ascii)
for i in range(v3):
    byte_data.append(v3)  # 当需要补几个字节的时候,就补上几个。如需要11个字节,就补11个11(ascii)

机智的解密取值

  • result[-1] 是取到了最后一个值,而我们加密的时候,补足的值和个数是相同的
  • result[0:result[-1]] 就代表真正的数据 牛逼!!!
result = cipher_jm.decrypt(msg)
result_data = result[0:-result[-1]]

例子:

result = b'\xe8\xa6\x81\xe5\x8a\xa0\xe5\xaf\x86\xe7\x9a\x84\xe5\xad\x97\xe7\xac\xa6\xe4\xb8\xb2\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' # result 是解密后的数据

print('最后一个字节',result[-1]) # 取最后一个值
print('真实的数据',result[0:-result[-1]])  # 获取真实的数据 result[0:-11]

还有一个问题是 v2取余是0的情况

v2取余是0的情况,加密的字符串正好就是16的倍数,那么解密的时候就出问题了,解决问题还得从加密的地方入手。思路是主动设置一个值,如16

v1 = len(byte_data)
v2 = v1 % 16  # 取余 5
if v2 == 0:
    v3 = 16 # 字符串正好是16的倍数 余数是0 主动设置成16 
else:
    v3 = 16 - v2  # 这是要补足的数 11 : 21+11=32 是16的倍数
for i in range(v3):
    byte_data.append(v3)

最终封装成函数

from Crypto.Cipher import AES


def encrypt(message):
    key = b'jlaksdflj77asdfh'  # 16个字节或16字节的倍数
    cipher = AES.new(key, AES.MODE_CBC, key)
    byte_data = bytearray(message, encoding='utf8')  # 想要动态修改字节,用bytearray 相当于把字节转换成一个数组
    v1 = len(byte_data)  # 这是要加密的数据的长度 21
    v2 = v1 % 16  # 取余 5
    if v2 == 0:
        v3 = 16
    else:
        v3 = 16 - v2  # 这是要补足的数 11 : 21+11=32 是16的倍数
    for i in range(v3):
        byte_data.append(v3)
    final_data = byte_data.decode('utf8')  # 把字节解码成字符串
    msg = cipher.encrypt(final_data)  # 进行加密  final_data必须是16个字节或16字节的倍数
    return msg


# ############## 解密 ##############

def decrypt(msg):
    key = b'jlaksdflj77asdfh'  # 需要同样的key
    cipher = AES.new(key, AES.MODE_CBC, key)
    result = cipher.decrypt(msg)  # 对字节进行解密
    data = result[0:-result[-1]]
    return data.decode('utf8')

if __name__ == '__main__':

    msg = encrypt("我要加密了")
    data = decrypt(msg)
    print(data)

API 加密和AES数据加密结合

客户端发送数据的形式

当客户端把采集的信心向API发送的时候,request.post(url,data,json) ,使用json的的时候默认在请求头中添加'content_type': 'application/json',会把字典类型的数据json序列化成字符串发送到API。

# 源码中
if not data and json is not None:
    # urllib3 requires a bytes-like body. Python 2's json.dumps
    # provides this natively, but Python 3 gives a Unicode string.
    content_type = 'application/json'
    body = complexjson.dumps(json)
    if not isinstance(body, bytes):
        body = body.encode('utf-8')

request就是相当于socket,在传输的过程中会把字符串转换成字节发送,所以后台接收的数据实际字节数据。在这里可以自己把数据用json序列化然后转换成字节,用data发送。

import requests
import json

data = {'k1': 'v1'}
data = bytes(json.dumps(data),encoding='utf8')
# response = requests.post("http://127.0.0.1:8000/api/asset.html", headers={'OpenKey': auth()},json={'k1':'v1'})
response = requests.post(
    "http://127.0.0.1:8000/api/asset.html",
     headers={'OpenKey': auth(), 'content_type': 'application/json'},
    data=data
)
print(response.text)

服务端接收数据并解密

    elif request.method == 'POST':
        server_info= decrypt(request.body)  # 对数据进行解密
        server_info = json.loads(server_info) # 对字符串反序列化成字典类型的数据
        print(server_info)

结合后的客户端

  • 把加密和验证制作成插件 lib/utils
from Crypto.Cipher import AES
import time
import hashlib
from config import settings


def encrypt(message):
    """
    数据加密函数
    :param message:
    :return:
    """
    key = b'jlaksdflj77asdfh'  # 16个字节或16字节的倍数
    cipher = AES.new(key, AES.MODE_CBC, key)
    byte_data = bytearray(message, encoding='utf8')  # 想要动态修改字节,用bytearray 相当于把字节转换成一个数组
    v1 = len(byte_data)  # 这是要加密的数据的长度 21
    v2 = v1 % 16  # 取余 5
    if v2 == 0:
        v3 = 16
    else:
        v3 = 16 - v2  # 这是要补足的数 11 : 21+11=32 是16的倍数
    for i in range(v3):
        byte_data.append(v3)
    final_data = byte_data.decode('utf8')  # 把字节解码成字符串
    msg = cipher.encrypt(final_data)  # 进行加密  final_data必须是16个字节或16字节的倍数
    return msg


def decrypt(msg):
    """
    数据解密函数
    :param msg:
    :return:
    """
    key = b'jlaksdflj77asdfh'  # 需要同样的key
    cipher = AES.new(key, AES.MODE_CBC, key)
    result = cipher.decrypt(msg)  # 对字节进行解密
    data = result[0:-result[-1]]
    return data.decode('utf8')


def auth():
    """
    API验证
    :return:
    """
    ctime = time.time()
    key = settings.AUTH_KEY
    new_key = '%s|%s' % (key, ctime)

    m = hashlib.md5()
    m.update(bytes(new_key, encoding='utf8'))  # python3中是字节
    md5_key = m.hexdigest()  # 加密后是字符串

    md5_key_time = '%s|%s' % (md5_key, ctime)  # 把当前时间发送后API,API利用时间进行加密,最后比较的是密文
    return md5_key_time

调用:

  • client/Base
  • 注意要有请求头,主要是包含OpenKey
class Base(object):
    def post_asset(self, server_info):
        data = encrypt(json.dumps(server_info))  # 把采集到的资产信息序列化化成字符串 加密后变成字节
        # requests.post(settings.API, json=server_info)  # 向API发送数据 json=() 内部json序列化
        requests.post(settings.API, data=data,headers={'OpenKey': auth(), 'content_type': 'application/json'})  # 向API发送数据 json=() 内部json序列化

结合后的服务端

  • server_info= decrypt(request.body) # 对数据进行解密
  • server_info = json.loads(server_info) # 对字符串反序列化成字典类型的数据
import json
import hashlib

import time
from django.shortcuts import render, HttpResponse
from repository import models
from api.service import PluginManager
from autoserver import settings


def decrypt(msg):
    from Crypto.Cipher import AES
    key = b'jlaksdflj77asdfh'  # 需要同样的key
    cipher = AES.new(key, AES.MODE_CBC, key)
    result = cipher.decrypt(msg)  # 对字节进行解密
    data = result[0:-result[-1]]
    return data.decode('utf8')

# 在全局(存在内存中)建立已访问列表,格式是{'key秘钥|时间':超时时间} 后期可以用redis memcache等直接设置超时时间
api_key_record = {

}

def asset(request):

    client_md5_citme = request.META.get('HTTP_OPENKEY') # 客户端发送过来的MD5和用于加密的时间
    # print(client_md5_citme)
    client_md5_key,client_time = client_md5_citme.split('|') # 进行分割

    # 第一关:时间验证
    client_time = float(client_time) # 把字符串类型的转化成数字
    server_time = time.time()

    if server_time - client_time > 10:
        return HttpResponse("小伙子,超时了,第一关没有通过验证")

    # 第二关:规则验证
    temp = '%s|%s'%(settings.AUTH_KEY,client_time)
    m = hashlib.md5()
    m.update(bytes(temp, encoding='utf8'))
    server_md5_key = m.hexdigest()  # 服务端利用客户端发送的时间进行MD5加密的数据

    if server_md5_key!= client_md5_key:
        return HttpResponse('第二关没有通过验证')

    # 在进入第三关之前删除api_key_record中的超时的值
    # 字典不能通过for循环中删除
    for k in list(api_key_record.keys()): # 需要把字典的keys()转换成列表
        v = api_key_record[k]
        if server_time > v:
            del api_key_record[k] # 如果超时,则删除api_key_record中相应的记录

    # 第三关:建立访问列表(全局)
    if client_md5_citme in api_key_record:
        return HttpResponse('第三关没有通过验证')
    else:
        api_key_record[client_md5_citme] = client_time + 10 # 设置超时时间 10s后超时,然后从列表中删除


    if request.method == "GET":
        aaa = "重要的数据"
        return HttpResponse(aaa)

    elif request.method == 'POST':
        server_info= decrypt(request.body)  # 对数据进行解密
        server_info = json.loads(server_info) # 对字符串反序列化成字典类型的数据
        # print(server_info)
        # 新资产信息
        # server_info = json.loads(request.body.decode('utf-8'))  # 数据存在于body中
        print(server_info)
        # 数据库中的老的资产信息
        hostname = server_info['basic']['data']['hostname']  # 获取主机名
        server_obj = models.Server.objects.filter(hostname=hostname).first()  # 获得server对象
        # print(server_obj)  # 这里获得是主机名
        if not server_obj:
            return HttpResponse('当前主机名在资产中未录入')
        asset_obj = server_obj.asset  # 直接获得是资产对象 一对一的关系


        PluginManager(hostname,server_info,asset_obj,server_obj).exec_plugin()

    return HttpResponse('...')
posted @ 2017-08-06 17:31  hzxPeter  阅读(400)  评论(0编辑  收藏  举报