Python 对称加密的使用
对称加密
概述:对称加密就是加密和解密使用同一个密钥;就好比. 我要给你邮寄一个箱子. 上面怼上锁. 提前我把钥匙给了你一把, 我一把. 那么我在邮寄之前就可以把箱子锁上. 然后快递到你那里. 你用相同的钥匙就可以打开这个箱子.
条件:加密和解密使用相同的密钥,那么加密和解密的两端就必须拥有密钥才可以;
常见的对称加密算法: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 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进制进行数据的再度加密;
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
以及其他数据传输的需求之中,数据都具备一定的加密性;
补充:适用对称加密的情况,虽然在一定程度上将信息进行了隐藏,但是由于对称加密的特性,加密和界解密的密钥是相同;前端程序中必然会适用密钥进行解密,也会对数据的安全性造成一些损失,但是相对于任何加密都不使用的情况,安全性还是非常高的;
继续努力,终成大器;