因为主管数据,数据安全问题比较常见,最近遇到一个业务场景,较多数据加密的处理,结合最近优化数据安全流程的问题,重新梳理下数据加密的知识。
原则还是那句话:代码尽可能短,讲的尽可能通俗。
考虑到加密算法的进步,文末特地放了一些新的文章,供大家交流学习。
数据加密:对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码为“密文”,只能在输入相应的密钥之后才能显示出原容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。 该过程的逆过程为解密,即将该编码信息转化为其原来数据的过程。
目前主流的分法有2类,对称加密、非对称加密,当然也还有分3类的,新增单向加密(Hash算法),取决于每个人的理解。
算法 | 对称加密 | 非对称加密 | 单向加密 |
---|---|---|---|
类别 | AES / DES / 3DES | RSA / DSA | MD5,SHA系列 |
描述 | 数据加解密用相同密钥 | 公钥加密,加解密不同密钥 | 只可加密不可解密 |
解决问题 | 数据的机密性 | 身份验证 | 数据的完整性 |
优点 | 加密速度快 | 加解密密钥不一致,公钥公开,密钥安全 | |
缺点 | 密钥一致容易泄露 | 加密速度慢 | |
安全性 | AES高 > 3DES中 > DES低 | 均高 | SHA-1高>MD中 |
速度 | AES快 > DES中 > 3DES慢 | RSA中>ECC慢 | MD5快>SHA-1慢 |
资源消耗 | 3DES高 > DES中 > AES低 | ECC高>RSA中 | |
适用范围 | 内部系统,适合大数据量M比特级/秒 | 小数据量/数据签名 | 极少 |
其他 | 密钥:AES(128/192/256位)/3DES(112/168位)/DES(56位) | 成熟度:高 |
因为平时主要用python处理,所以主要介绍下常用的包
模块名 | 描述 |
---|---|
base64 | 用于二进制数据与ASCII字符的转换操作,提供了基于Base16, Base32, 和Base64算法以及实际标准Ascii85和Base85的编码和解码函数 |
hashlib | 提供常见的单向加密算法(如MD5,SHA等),每种算法都提供了与其同名的函数实现 |
hmac | 实现hmac单向加密算法,支持设置一个额外的密钥(通常被称为’salt’)来提高安全性 |
secrets | Python3.6 新增的模块,用于获取安全随机数 |
pycrypto | 持单向加密、对称加密和公钥加密以及随机数操作,该包是Python中密码学最有名的,2012年已停止。幸运的是,该项目的分支PyCrytodome 取代了 PyCrypto。 |
import base64
# 编码 encode
# 想将字符串转编码成base64,要先将字符串转换成二进制数据
tmp = "this is a test message:四叔的旅途."
res = tmp.encode("utf-8") # 默认以 utf-8 编码
res_code = base64.b64encode(res) # 被编码的参数必须是二进制数据
print(res_code)
# 解码 decode
res_code = "b'dGhpcyBpcyBhIHRlc3QgbWVzc2FnZS4="
res = base64.b64decode(res_code).decode("utf-8")
print(res)
一种信息摘要算法,以512位分组来处理输入的信息,每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
简单来说,即把一段信息(明文),通过一种有损的方式压缩成定长的字符(32位密文)。因为这种压缩方式是会损失信息的,所以是无法还原出“明文”的,即不可逆。
虽然无法从数学上破解MD5算法,但由于现在计算机具备强大的计算能力,可以通过“穷举法”破解该算法。如果想用该算法加密,还可以通过**“加盐”**的方式提高解密难度。算法允许多传一个参数"salt",指定通过MD5加密的次数,这样是能够提高解密难度的。
常用于不可还原的密码存储、信息完整性校验,如:
# 由于MD5模块在python3中被移除,在python3中使用hashlib模块进行md5操作。
# pip install md5/import md5 会报错
# ERROR: Could not find a version that satisfies the requirement md5
# ERROR: No matching distribution found for md5
# 写法1
import hashlib
tmp_text = 'test message:四叔的旅途'
md5 = hashlib.md5() # 生成MD5对象
md5.update(tmp_text.encode('utf-8')) # 对数据加密 该方法只接受byte类型,否则会报错,所以要在参数前添加b来转换类型的原因, 后面方法
tmp_text = md5.hexdigest() # 获取密文 hexdigest 十六进制的意思
print(tmp_text)
# or:
tmp_text = 'test message:四叔的旅途'
tmp_text = hashlib.md5(bytes(tmp_text,encoding = 'utf-8')).hexdigest()
# 不同写法
tmp_text = hashlib.md5(tmp.encode("utf-8")).hexdigest() # 最简单写法,推荐☆
tmp_pic = hashlib.md5(b'test message:四叔的旅途').hexdigest() # 最常见的写法,常用于图片的命名,因为该方法只接受byte类型,否则会报错,所以要在参数前添加b来转换类型的原因
tmp_text = hashlib.new('md5', b'test message:四叔的旅途').hexdigest() # hashlib.new(name[, data]),name传入的是哈希加密算法的名称,如md5
# MD5加盐值(SALT)
tmp_text = 'test message:四叔的旅途'
# 生成MD5对象
md5 = hashlib.md5(b'~!@@#!#$DFDT@#$@#')
# 以下两种方式与上面效果等同
# md5 = hashlib.md5('~!@@#!#$DFDT@#$@#'.encode('utf-8'))
# md5 = hashlib.md5(bytes('~!@@#!#$DFDT@#$@#',encoding='utf-8'))
安全哈希算法(英语:Secure Hash Algorithm,缩写为SHA)一个密码哈希函数家族,是FIPS所认证的安全哈希算法。
SHA与MD5的不同:
# md5和sha1 同一个包,均为不可逆算法,所以写法上基本一致;
import hashlib
tmp = "test message:四叔的旅途"
tmp_text = hashlib.sha1(tmp.encode("utf-8")).hexdigest()
print("sha1加密前为 :",tmp)
print("sha1加密前后 :",tmp_text)
高级加密标准(Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法。速度快,编码紧凑。
AES技术是一种对称的分组加密技术,使用128位分组加密数据,提供比WEP/TKIPS的RC4算法更高的加密强度,其加密码表和解密码表是分开的,并且支持子密钥加密,这种做法优于以前用一个特殊的密钥解密的做法。
AES算法优点:
加密方和解密方保有同一个16位长的密钥,使用AES算法加解密时需要传入该密钥参数,通过Java实现AES算法提供的工具包加密后返回的是一个Base64格式的字节数组,因此为保证密文“可读性”,需要在加密后对密文进行Base64编码,解密前进行Base64解码成密文。
AES算法的安全性,取决于密钥的安全性。因此一定不要在加解密的URL中传入该密钥参数,不然没有意义。一般的做法是:前后端协商好密钥,或者通过不对称加密的方式传递密钥。
AES本质上是一个基本算法,实现AES有5种模式:ECB、CBC、CFB和OFB、CTR,最常用的是ECB 和 CBC 模式。
ECB模式(电子密码本模式:Electronic codebook)
最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
CBC模式(密码分组链接:Cipher-block chaining)
对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。
CFB模式(密文反馈:Cipher feedback)
与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文(Stream Cipher)。
OFB模式(输出反馈:Output feedback)
OFB是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。
这里代码实现的坑有点多,务必注意几个事项:
"""
ECB模式,不需要iv偏移量参数
"""
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
# 16bits 不足的空格补16的倍数 不写这个会采坑
def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('\0' * add)
return text.encode('utf-8')
# 加密函数
def enCrypt(text):
key = '1234567891234567'.encode('utf-8') # 需16bits
mode = AES.MODE_ECB # 模式确认
text = add_to_16(text)
cryptos = AES.new(key, mode) # 加密操作
cipher_text = cryptos.encrypt(text) # 密文
return b2a_hex(cipher_text)
# 解密后,用strip()去掉补足的空格,不去解密后结果不一致
def deCrypt(text):
key = '1234567891234567'.encode('utf-8') # 加解密需同一个
mode = AES.MODE_ECB # 同一个key需同一个模式解
cryptor = AES.new(key, mode)
plain_text = cryptor.decrypt(a2b_hex(text)) # 解密后原文,因为AES加密时候得到的字符串不一定是ascii字符集的,输出or保存时候可能报错,所以这里统一把加密后的字符串转化为16进制字符串
return bytes.decode(plain_text).rstrip('\0')
if __name__ == '__main__':
jia_m = enCrypt("test message:四叔的旅途") # 加密
jie_m = deCrypt(jia_m) # 解密
print("原文加密:", jia_m)
print("密文解密:", jie_m)
"""
原文加密: b'c554fb642755944d9b890f90bcbc37d0ca26f34cf3fb7cbf1d446ba75148b4ea'
密文解密: test message:四叔的旅途
"""
"""
CBC模式,参数要齐 + iv偏移量参数
在参数前添加b来转换类型的原因:数据加密方法 接受byte类型,否则会报错,前文md5中updata有提;
"""
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
# 16bits 不足的空格补16的倍数 不写这个会采坑
def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('\0' * add)
return text.encode('utf-8')
# 加密函数
def enCrypt(text):
key = '1234567891234567'.encode('utf-8') # 同样16bits
mode = AES.MODE_CBC
iv = b'abcdefghijklmnop' # 偏移量,参见前文加b原因
text = add_to_16(text) # 文本补足16bits
cryptos = AES.new(key, mode, iv)
cipher_text = cryptos.encrypt(text) # 解密后原文,因为AES加密时候得到的字符串不一定是ascii字符集的,输出or保存时候可能报错,所以这里统一把加密后的字符串转化为16进制字符串
return b2a_hex(cipher_text)
# 解密后,用strip()去掉补足的空格
def deCrypt(text):
key = '1234567891234567'.encode('utf-8')
iv = b'abcdefghijklmnop'
mode = AES.MODE_CBC # 同一个key需同一个模式解
cryptos = AES.new(key, mode, iv)
clear_text = cryptos.decrypt(a2b_hex(text)) # 解密后原文
return bytes.decode(clear_text).rstrip('\0')
if __name__ == '__main__':
jia_m = enCrypt("test message:四叔的旅途") # 加密
jie_m = deCrypt(jia_m) # 解密
print("原文加密:", jia_m)
print("密文解密:", jie_m)
"""
原文加密: b'c07638565f2944ae4db4366d361f4af42b65fed9f4df0589e20461e5f5618814'
密文解密: test message:四叔的旅途
"""
DES算法:密码体制中的对称密码体制,又被称为美国数据加密标准,是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。
DES加密过程:
接收一个明文盒一个64位的密钥key,明文字符串会被转换为对各64位的块,加密过程以块位单位,经过初态转换,16轮循环加密,终态转换,最终每个64位的块都会被加密成一个64位的密文块,将得到的密文块拼起来,得到的就是最终加密后的结果。
具体算法流程图如下 : orz…看了n遍才看懂…
"""
查了好多资料,DES 有比较成熟的包,不搞一堆循环加密的代码...
"""
# pip install pyDes
from pyDes import des, CBC, PAD_PKCS5
import binascii
# 秘钥 真的是采坑不断....
# 在使用pyDes实现DES加密时,初始化des是必须要8位密码的,超过8位报错常见于使用Python实现JavaScript的场景中,从JavaScript获取的密钥有时不止8位... MD..Python问题...
# 解决办法:可通过使用一个随意的8位密钥先初始化des对象,然后调用setkey()方法传入超过8位的密钥重置
# from pyDes import des, PAD_PKCS5
# import base64
# encry = des("0" * 8)
# encry.setKey("123456789") # 可设置超过8bits
# result = encry.encrypt("密文".encode(), padmode=PAD_PKCS5)
# base = base64.b64encode(result).decode()
# print '2xC7EUPxQY4='
key = 'lz@lzljn'
def enCrypt(s):
"""
DES 加密
:param s: 原始字符串
:return: 加密后字符串,16进制
"""
iv = key # 偏移
# secret_key:加密密钥,CBC:加密模式,iv:偏移, padmode:填充
des_obj = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
# 返回为字节
secret_bytes = des_obj.encrypt(s, padmode=PAD_PKCS5)
# 返回为16进制
return binascii.b2a_hex(secret_bytes)
def deCrypt(s):
"""
DE 解密
:param s: 加密后的字符串,16进制
:return: 解密后的字符串
"""
iv = key
des_obj = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
decrypt_str = des_obj.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
return decrypt_str
print("原文加密:",enCrypt("iloveullllz"))
print("密文解密:",deCrypt("3b0754717de0a05d7cb6ae88125c09d5"))
"""
b'3b0754717de0a05d7cb6ae88125c09d5'
b'iloveullllz'
"""
3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称,是DES向AES过渡的加密算法,此处不做详细介绍了,基本和上面一致。
RSA加密算法:一种非对称加密算法, 使用openssl ,keytools等工具生成一对公私钥对,使用被公钥加密的数据可以使用私钥来解密。
1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。用他们三个人的名字命名叫RSA算法。从那时直到现在,RSA算法一直是最广为使用的"非对称加密算法"。毫不夸张地说,只要有计算机网络的地方,就有RSA算法。
RSA算法非常可靠,密钥越长,越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。即长度超过768位的密钥,还无法破解(至少没人公开宣布),因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。
基于一个数论事实:将两个大质数p,q相乘,对其乘积n = p*q进行因式分解极其困难,因此可以将乘积n 进制化后公开作为加密密钥,密钥一般1024位起。
欧拉函数φ(n):任意给定正整数n,请问在小于等于n的正整数之中,计算有多少个与n构成互质关系的方法(比如,在1到8之中,与8形成互质关系的是1、3、5、7,所以 φ(n) = (p-1)*(q-1) = 4)
RSA的公钥、私钥的组成,以及加密、解密的公式可见于下表:
模块名 | 解密公式 |
---|---|
公钥KU | n: 两素数p和q的乘积 – e: 与(p-1)(q-1)互质,1 |
私钥KR | d: d ≡e-1 mod f(n) – n: 两素数p和q的乘积 |
加密 | C ≡ m^e mod n |
解密 | m ≡ C^e mod n |
注:≡ 恒等号一般用于一些参变量恒为一个常数或恒定表达式时,总等于关系与变量无关。例 f(x)≡k 表示该函数的值始终为k而与x的值无关.
公钥与私钥的生成:
加密信息:
import rsa # 现成的包可用
# rsa 加密
def rsaEncrypt(clear_text):
(pubkey, prikey) = rsa.newkeys(512) # 生成公钥、私钥,()随机数
# key = rsa.newkeys(1000)
# key[0] = pubkey key[1] = prikey
print("rsa加密公钥: ", pubkey)
print("rsa加密私钥: ", prikey)
clear_text = clear_text.encode('utf-8') # 明文编码格式
cipher_text = rsa.encrypt(clear_text, pubkey) # 明文通过公钥加密生成密文
return (cipher_text, prikey) # 传回密文,prikey 私钥
# rsa 解密
def rsaDecrypt(cipher_text, prikey):
clear_text = rsa.decrypt(cipher_text, prikey) # 密文通过私钥解密
clear_text = clear_text.decode('utf-8') # 明文编码
return clear_text # 传回明文
(a, b) = rsaEncrypt("test message:四叔的旅途")
print('加密后密文:',a)
print('私钥:',b)
clear_text = rsaDecrypt(a, b)
print('解密后明文:',clear_text)
"""
rsa公钥: PublicKey(10663336662571050099297238857296617461672702659070145995594813556835299127631313673297344714201089161781272686456843997221628596317501872676983803305119951, 65537)
rsa私钥: PrivateKey(10663336662571050099297238857296617461672702659070145995594813556835299127631313673297344714201089161781272686456843997221628596317501872676983803305119951, 65537, 9674890895674195965705058469758632032378385423709187500039043650404207642663541372623922368423209614653394571329100027988392186373717565032205852814673473, 7567398718848405743192951986927239943286617179940639622213395272396044025056814751, 1409115213661396592372119588194019684418400115364502684174591606711605201)
"""
"""
RSA算法的核心代码分为3个部分:计算最大公约数、大整数幂取模算法、公钥私钥生成及加解密。
"""
# -*- coding: utf-8 -*-
# 求两个数字的最大公约数 即欧几里得算法gcd 百度说明:https://baike.baidu.com/item/%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97%E7%AE%97%E6%B3%95/1647675?fr=aladdin
###########################################
# Part_1: 获取a,b的最大公约数
def gcd(a, b):
while a != 0:
a, b = b % a, a
return b
# 扩展欧几里得算法 作用:得到ax+by=gcd(a,b)的解
def ext_gcd(a, b):
if b == 0:
return 1, 0, a
else:
x, y, q = ext_gcd(b, a % b) # 递归直至余数等于0(需多递归一层用来判断)
# q = gcd(a, b) = gcd(b, a%b)
x, y = y, (x - (a // b) * y) # 欧几里得算法反向推导每层a、b的因子使得gcd(a,b)=ax+by成立
return x, y, q
###########################################
# Part_2: 大整数幂取模算法
# -*- coding: utf-8 -*-
def exp_mode(base, exponent, n):
bin_array = bin(exponent)[2:][::-1]
r = len(bin_array)
base_array = []
pre_base = base
base_array.append(pre_base)
for _ in range(r - 1):
next_base = (pre_base * pre_base) % n
base_array.append(next_base)
pre_base = next_base
a_w_b = __multi(base_array, bin_array, n)
return a_w_b % n
def __multi(array, bin_array, n):
result = 1
for index in range(len(array)):
a = array[index]
if not int(bin_array[index]):
continue
result *= a
result = result % n # 加快连乘的速度
return result
###########################################
# Part_3: 公钥私钥生成
from gcd import ext_gcd
from exponentiation import exp_mode
# 公钥与私钥 生产的步骤
def rsaKey(p, q):
n = p * q # 选取2个质数,n = p * q φ(n) = (p-1) * (q-1)。
f = (p - 1) * (q - 1) # φ(n) = (p-1) * (q-1)。
e = 65537
k, d, y = ext_gcd(e, f) # ed + φ(n)k = 1 y最大公约数
if d < 0: # 因为 e*d ≡ 1 (mod φ(N)) 所以d>0
d = d + f
return (n, e), (n, d)
# rsa 加密
def enCrypt(clear_text, pubkey):
n = pubkey[0]
e = pubkey[1]
cipher_text = exp_mode(clear_text, e, n)
return c
# rsa 解密
def decCrypt(cipher_text, selfkey):
n = selfkey[0]
d = selfkey[1]
clear_text = exp_mode(cipher_text, d, n)
return clear_text
if __name__ == "__main__":
p = 3 # 一般p,q需超1024位
q = 5
pubkey, prikey = rsaKey(p, q)
clear_text = '明文'
cipher_text = enCrypt(clear_text, pubkey)
print('加密后密文:', cipher_text)
clear_text = decCrypt(cipher_text, prikey)
print('解密后明文:', clear_text)
数据安全在互联网的趋势发展下,越来越重要,加密算法很多时候是后端和运维要去处理和加密的,但是作为一个从事数据领域的来说,对于基本的数据理解和数据安全的基础知识,还是需要有一定认知的。
以上在梳理和总结的时候,细节遗忘比较多,也参考了以下几个不错的文章,供大家共享学习。
参考文献:
加密算法:https://blog.csdn.net/baidu_22254181/article/details/82594072
加密算法:https://mp.weixin.qq.com/s/s1Qe66RuvjDHw65mkT0DLg
RSA通俗易懂:https://blog.csdn.net/chroje/article/details/79477329
RSA的攻与防:https://www.packetmania.net/2020/12/01/RSA-attack-defense/ ;
###### 题外话
希望本文有大家有帮助, 如有错误,欢迎指正。
转载请注明原文链接:
https://blog.csdn.net/weixin_41613094/article/details/122061095