对称加密
概述:对称加密就是加密和解密使用同一个密钥;就好比. 我要给你邮寄一个箱子. 上面怼上锁. 提前我把钥匙给了你一把, 我一把. 那么我在邮寄之前就可以把箱子锁上. 然后快递到你那里. 你用相同的钥匙就可以打开这个箱子.
条件:加密和解密使用相同的密钥,那么加密和解密的两端就必须拥有密钥才可以;
常见的对称加密算法: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。这时候怎么办呢?就需要对明文块进行填填充。
加密方式:分组密码加密方式主要有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 pycryptodomepip install Crypto
简单使用
# -*- coding: utf-8 -*-from Crypto.Cipher import AES# 1. 创建加密器,传入密钥(字节的格式),传入加密的模式, 根据模式的不同,确定是否需要IVaes_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) % 16data_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 AESfrom Crypto.Util.Padding import pad# CBC 模式的使用,构建加密对象的时候需要使用IVaes = 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, DES3from Crypto.Util.Padding import pad, unpadclass 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 resultif __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进制进行数据的再度加密;
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 base64s = "养天地正气,法古今完人"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 base64class 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 resultif __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 base64import jsonfrom Crypto.Cipher import AES, DESfrom Crypto.Util.Padding import padfrom flask import Flask, jsonifyapp = 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_dataif __name__ == "__main__": app.run()
说明:上述的代码逻辑不仅仅适用与Flask
之中,在Django
以及其他数据传输的需求之中,数据都具备一定的加密性;
补充:适用对称加密的情况,虽然在一定程度上将信息进行了隐藏,但是由于对称加密的特性,加密和界解密的密钥是相同;前端程序中必然会适用密钥进行解密,也会对数据的安全性造成一些损失,但是相对于任何加密都不使用的情况,安全性还是非常高的;
继续努力,终成大器;