对称加密

对称加密

概述:对称加密就是加密和解密使用同一个密钥;就好比. 我要给你邮寄一个箱子. 上面怼上锁. 提前我把钥匙给了你一把, 我一把. 那么我在邮寄之前就可以把箱子锁上. 然后快递到你那里. 你用相同的钥匙就可以打开这个箱子.

条件:加密和解密使用相同的密钥,那么加密和解密的两端就必须拥有密钥才可以;

常见的对称加密算法:AES, DES ,3DES;

1.Python 使用对称加密解密

对称加密中有很高的相似性,这里我们以 AES 为例,讲解 AES 中的概念等信息;

1.1 AES

1.1.1 AES 介绍

密钥: AES 支持 3 种长度的密钥 128位、192位、256位;实际上就是指的AES算法对不同长度密钥的使用。

填充:要想了解填充的概念,必须先知道 AES 分组加密特性。AES 算法在对明文加密的时候,并不是一次把全部的明文进行假面,而是把明文拆分成一个个独立明文模块,每一个明文快的长度是128bit。这些明文块经过 AES 加密器的复杂处理,生成一个个独立的密文块,这些密文块拼接在一起,就是最终的AES加密结果。假如一段明文长度是192bit,如果按每128bit一个明文块来拆分的话,第二个明文块只有64bit,不足128bit。这时候怎么办呢?就需要对明文块进行填填充。

img

加密方式:分组密码加密方式主要有7种:ECB,CBC,CFB,OFB和CTR,其中最常用的是ECB 和 CBC;

  • ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
  • CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或或操作后再加密,这样做的目的是增强破解难度。
  • CFB/OFB:实际上是一种反馈模式,目的也是增强破解的难度。
  • FCB和CBC的加密结果是不一样的,两者的模式不同,而且CBC会在第一个密码块运算时加入一个初始化向量。

IV:初始化向量IV,在除 ECB 意外的所有加密方式中,都需要用到 IV 对加密结果进行随机化;特别注意:ECB 模式不需要 IV 进行操作

1.1.2 Python 使用AES

  • 安装

    pip install pycryptodome
    pip install Crypto
    
  • 简单使用

    # -*- coding: utf-8 -*-
    from Crypto.Cipher import AES
    
    
    # 1. 创建加密器,传入密钥(字节的格式),传入加密的模式, 根据模式的不同,确定是否需要IV
    aes_object = AES.new(b"asdfghjklqwertyu", mode=AES.MODE_ECB)
    data = "吃了吗?"  # 明文信息
    data_bs = data.encode("utf-8")  # 被加密的数据必须是字节形式的数据信息
    # AES 加密的时候需要满足字节必须是 16的倍数;
    # 填充规则: 缺少数量的个数 * chr(缺少数据量个数)
    
    pad_len = 16 - len(data_bs) % 16
    data_bs += (pad_len * chr(pad_len)).encode("utf-8")
    bs = aes_object.encrypt(data_bs)  # 加密或者解密的时候需要的是字节形式的数据
    print(bs)  # b'_\xc2K\xb2\x06Q\x90\xf52\xa2!Q\x05t\th'
    
  • CBC 模式的使用

    # -*- coding: utf-8 -*-
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad
    
    # CBC 模式的使用,构建加密对象的时候需要使用IV
    aes = AES.new(key="abcdefghijklmn12".encode(), mode=AES.MODE_CBC, IV=b'0123456789012345')
    # 声名明文信息,被加密的信息通常是字节的形式
    data = "封印松动了,上水泥".encode("utf-8")
    # 如果AES 每次加密的长度是16,把数据填充成16的倍数;
    data = pad(data, 16)  # 调用第三方的填充模块,常规情况下,填充倍数都是16
    # 进行信息的加密处理;
    result = aes.encrypt(data)
    print(result) 
    result = base64.b64encode(result)
    print(result)
    # b'37f+HQNazFDAz8ZjnfXw17/55HfllEe4cJLcIhG312o='
    

    在这种模式下获取的信息都是字节的信息,我们无法进行数据的传输,在网页中使用的时候,通常会使用bs64将数据转换成字符串的信息,进行数据的传输;

1.1.3 AES 解密

解密上述案例中的密文信息,将信息进行还原;

data = b'37f+HQNazFDAz8ZjnfXw17/55HfllEe4cJLcIhG312o='
aes = AES.new(key="abcdefghijklmn12".encode(), mode=AES.MODE_CBC, IV=b'0123456789012345')
# 将数据进行 base64 解码
data = base64.b64decode(data)
# 进行AES的解密;
bs = aes.decrypt(data)
print(bs.decode())
# 打印结果信息存在填充的数据,进行填充的去除
result = unpad(bs, 16)  # 一般情况下都是16,根据加密的情况进行变化
print(result.decode())  # 将结果进行解码,转换成字符串的信息;

总结:当使用 AES 加密(解密)的时候,必须要关注的四个值明文(密文)密钥 key模式(ECB/CBC)IV

3.2 DES

DES 与 AES 加密的使用方式相同,包括DES3 与 AES 的用途也是相同,可以理解为对称加密的时候,代码逻辑都是相同,传入数据、选择模式、设置密钥、设置随机向量、加密(解密)、编码;

加密解密流程:

  • 加密
    • 实例化加密器
    • 将信息转换成字节信息
    • 进行数据的填充
    • 进行加密
  • 解密
    • 实例化解密器
    • 处理字节信息
    • 将数据进行解密(先解密)
    • 去除数据的填充

3.2.1 加密解密的使用

# -*- coding: utf-8 -*-

from Crypto.Cipher import AES, DES, DES3
from Crypto.Util.Padding import pad, unpad


class SymEncrypt(object):

    @staticmethod
    def get_des_encrypt(data: str, key: bytes, mode: str = None, iv: bytes = None) -> bytes:
        """
        返回DES加密后的数据信息;
        :param data: str; 被加密的明文信息;
        :param key: bytes; 密钥信息,长度通常是8位;
        :param mode: 加密模式,本模块暂时值封装常用的情况 ECB 和 CBC;
        :param iv: bytes; 当模式是 ECB 的时候默认是None,DES中常用的长度是8;
        :return: bytes; 返回加密后的字节数据;
        """
        # 处理两种不同的模式信息;
        if mode == "ECB":
            # 本模式之下不需要使用 iv 的随机向量值
            # 1.创建加密器
            des = DES.new(key=key, mode=DES.MODE_ECB)
            # 2.进行数据的填充,与字节转换;
            data = pad(data.encode(), 8)
            # 获取加密结果并返回
            result = des.encrypt(data)
            return result
        else:
            # 其他模式的情况下需要使用iv值,当前使用的是CBC模式,可以使用 eval()函数扩展其他模式;
            des = DES.new(key=key, mode=DES.MODE_CBC, IV=iv)
            data = pad(data.encode(), 8)
            result = des.encrypt(data)
            return result

    @staticmethod
    def get_des_decrypt(data: bytes, key: bytes, mode: str = None, iv: bytes = None) -> bytes:
        """
        返回DES解密后的数据信息;
        :param data: str; 被加密的密文信息;
        :param key: bytes; 密钥信息,长度通常是8位;
        :param mode: 加密模式,本模块暂时值封装常用的情况 ECB 和 CBC;
        :param iv: bytes; 当模式是 ECB 的时候默认是None,DES中常用的长度是8;
        :return: bytes; 返回加密后的字节数据;
        """
        # 处理两种不同的模式信息;
        if mode == "ECB":
            # 本模式之下不需要使用 iv 的随机向量值
            # 1.创建加密器
            des = DES.new(key=key, mode=DES.MODE_ECB)
            # 2.进行数据的填充,与字节转换;
            # 对数据进行解密
            data = des.decrypt(data)
            # 解密完成之后,将填充好的数据进行去除
            result = unpad(data, 8)
            return result
        else:
            # 其他模式的情况下需要使用iv值,当前使用的是CBC模式,可以使用 eval()函数扩展其他模式;
            des = DES.new(key=key, mode=DES.MODE_CBC, IV=iv)
            data = des.decrypt(data)
            # 先解密后去除填充;
            result = unpad(data, 8)
            return result


if __name__ == '__main__':
    v = SymEncrypt.get_des_encrypt("昨天吃多了", "abcdefgh".encode(), mode="CBC", iv=b"01234567")
    print(v)  # 加密
    import base64

    v = base64.b64encode(v)  # 进行 base64编码
    print(v.decode())  # bas64 可以编译成 字符串的形式
    # 进行base64解码
    sv = base64.b64decode(v.decode())
    ssv = SymEncrypt.get_des_decrypt(sv, key="abcdefgh".encode(), mode="CBC", iv=b"01234567")
    print(ssv)  # 解密完成
    print(ssv.decode())  # 字节的转换

2.前端中使用对称加密

前端中使用加密算法的时候主要使用的是Crypto;需要注意该框架在解密的时候需要传入的是base64编码后的数据,否则会报错;

const CryptoJS = require('crypto-js')
const key = CryptoJS.enc.Utf8.parse("1234123412ABCDEF");  // 设置16位的秘钥信息
const iv = CryptoJS.enc.Utf8.parse('ABCDEF1234123412'); // 设置16位的随机向量


// 数据加密
export function Encrypt(data) {
    // 数据处理, sigBytes 对象
    let srcs = CryptoJS.enc.Utf8.parse(data);
    // 设置加密的模式,填充的方式;
    let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    // 返回加密的结果并设置全部的字母转换成为大写的字母信息
    return encrypted.ciphertext.toString().toUpperCase();
}

// 数据解密
function Decrypt(data) {
    // Hex 为16进制的数组
    let encrypteHexstr = CryptoJS.enc.Hex.parse(data);
    console.log(encrypteHexstr)
    // 将信息进行 Base64 编码;
    let srcs = CryptoJS.enc.Base64.stringify(encrypteHexstr);
    console.log(srcs)
    // 进行解密,crypto 框架在进行解密的时候需要传入base64的编码
    let decrypt = CryptoJS.AES.decrypt(srcs, key, {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
    console.log(decrypt)
    let decryptstr = decrypt.toString(CryptoJS.enc.Utf8);
    console.log(decryptstr.toString())
    return decryptstr.toString();
}
export default {
    Encrypt,
    Decrypt

}

3.交互使用

前后端交互使用的时候,通常不会直接传输加密后的字节信息,通常传输base64编码后的信息,或者将字节信息转换成16进制进行数据的再度加密;

image-20221127160010178

3.1 base64

base64 其实很容易理解. 通常被加密后的内容是字节. 而我们的密文是用来传输的(不传输谁加密啊).
但是, 在http协议里想要传输字节是很麻烦的一个事儿. 相对应的. 如果传递的是字符串就好控制的多. 此时base64就应运而生了. 26个大写字母+26个小写字母+10个数字+2个特殊符号(+和/)组成了一组类似64进制的计算逻辑. 这就是base64了.

: base64长度要求. 字符串长度必须是4的倍数;当长度不够的时候通常使用=在最后进行填充,等号的数量是小于4的;

# base64的组成
(A-Z) + (a-z) + (0-9) + (+ /) 

python使用base64加密

import base64

s = "养天地正气,法古今完人"
s += ("=" * (4 - len(s) % 4))
print(s)  # 编码前
data = base64.b64encode(s)
print(data)  # 编码后
v = base64.b64decode(data).decode("utf-8")
print(v)  # 解码

简单封装一下作为工具使用;

# -*- coding: utf-8 -*-
"""base64 编码转换工具,主要是根据解码的字节形式不同进行封装;
"""
import base64


class BaseUtils(object):

    @staticmethod
    def base_encode(string: str, mode: str = None) -> str:
        """
        base64 编码;
        :param string: 被编码信息;
        :param mode: 被编码信息的字节编码格式;
        :return: str; 编码后的字符串信息;
        """
        # 检查数据是否需要进行填充
        string += ("=" * (4 - len(string) % 4))
        # 进行base64的编码,并将编码后的字节转换成字符串信息
        if mode is None:
            result = base64.b64encode(string.encode()).decode()
            return result
        result = base64.b64encode(string.encode(encoding=mode)).decode(encoding=mode)
        return result

    @staticmethod
    def base_decode(string: str, mode: str = None):
        """
        base64 解码;
        :param string: 被解码的信息
        :param mode: 解码后的格式信息;
        :return: 解码后的数据信息;
        """
        if mode is None:
            return base64.b64decode(string.encode()).decode()
        result = base64.b64decode(string.encode(encoding=mode)).decode(encoding=mode)
        return result


if __name__ == '__main__':
    """脚本测试执行
    """
    # 编码
    data = "sssss"
    v = BaseUtils.base_encode(data)
    print(v)
    # 解码
    sv = BaseUtils.base_decode(v)
    print(sv)

3.2 flask 使用加密技术

# -*- coding: utf-8 -*-
import base64
import json

from Crypto.Cipher import AES, DES
from Crypto.Util.Padding import pad
from flask import Flask, jsonify

app = Flask(__name__)


def get_aes_encrypt(data: str, key: bytes, mode: str = None, iv: bytes = None) -> bytes:
    """
    返回AES加密后的数据信息;
    :param data: str; 被加密的明文信息;
    :param key: bytes; 密钥信息,长度通常是16;
    :param mode: 加密模式,本模块暂时值封装常用的情况 ECB 和 CBC;
    :param iv: bytes; 当模式是 ECB 的时候默认是None;
    :return: bytes; 返回加密后的字节数据;
    """
    # 处理两种不同的模式信息;
    if mode == "ECB":
        # 本模式之下不需要使用 iv 的随机向量值
        # 1.创建加密器
        aes = AES.new(key=key, mode=DES.MODE_ECB)
        # 2.进行数据的填充,与字节转换;
        data = pad(data.encode(), 16)
        # 获取加密结果并返回
        result = aes.encrypt(data)
        return result
    else:
        # 其他模式的情况下需要使用iv值,当前使用的是CBC模式,可以使用 eval()函数扩展其他模式;
        aes = AES.new(key=key, mode=DES.MODE_CBC, IV=iv)
        data = pad(data.encode(), 16)
        result = aes.encrypt(data)
        return result


@app.route("/home")
def home():
    data = {"msg": "hello word"}
    # return jsonify(data)  # 明文返回会直接暴露数据;
    # 将数据进行加密后返回
    encrypt_data = get_aes_encrypt(json.dumps(data), key=b"0123456789012345", mode="ECB")
    # 将信息处理成base64的字符串形式,http对字符串的支持要好于字节
    encrypt_data = base64.b64encode(encrypt_data).decode()
    return encrypt_data


if __name__ == '__main__':
    app.run()

说明:上述的代码逻辑不仅仅适用与Flask之中,在Django以及其他数据传输的需求之中,数据都具备一定的加密性;

image-20221130210451549

补充:适用对称加密的情况,虽然在一定程度上将信息进行了隐藏,但是由于对称加密的特性,加密和界解密的密钥是相同;前端程序中必然会适用密钥进行解密,也会对数据的安全性造成一些损失,但是相对于任何加密都不使用的情况,安全性还是非常高的;

继续努力,终成大器;

posted @ 2022-12-08 23:46  紫青宝剑  阅读(128)  评论(0编辑  收藏  举报