Python之数据加密与解密及相关操作(hashlib模块、hmac模块、random模块、base64模块、pycrypto模块)

时间:2023-02-21 14:27:08

本文内容


  1. 数据加密概述
  2. Python中实现数据加密的模块简介
  3. hashlib与hmac模块介绍
  4. random与secrets模块介绍
  5. base64模块介绍
  6. pycrypto模块介绍
  7. 总结
  8. 参考文档

提示: Python 2.7中的str是字节串,而Python 3.x中的str是字符串。本文中的代码都是通过Python 2.7实现的,如果你使用的是Python 3.x,由于下面大部分加密与解密函数的参数都要求是字节对象,因此在调用下面介绍的加解密函数时,可能需要先将字符串参数转换为字节对象。

一、数据加密概述


1. 网络数据传输面临的威胁

网络安全涉及很多方面,而网络数据的安全传输通常会面临以下几方面的威胁:

  • 数据窃听与机密性: 即怎样保证数据不会因为被截获或窃听而暴露。
  • 数据篡改与完整性: 即怎样保证数据不会被恶意篡改。
  • 身份冒充与身份验证: 即怎样保证数据交互双方的身份没有被冒充。

2. 相应的解决方案

针对以上几个问题,可以用以下几种数据加密方式来解决(每种数据加密方式又有多种不同的算法实现):

数据加密方式 描述 主要解决的问题 常用算法
对称加密 指数据加密和解密使用相同的密钥 数据的机密性 DES, AES
非对称加密 也叫公钥加密,指数据加密和解密使用不同的密钥--密钥对儿 身份验证 DSA,RSA
单向加密 指只能加密数据,而不能解密数据 数据的完整性 MD5,SHA系列算法

需要说明的是,上面SHA系列算法是根据生成的密文的长度而命名的各种算法名称,如SHA1(160bits)、SHA224、SHA256、SHA384等。我们常听说的MD5算法生成的密文长度为128bits。

关于上面提到的这些内容,大家可以参考《网络数据传输安全及SSH与HTTPS工作原理》这篇博文来了解更多的信息。本文主要讨论的问题是,我们如何使用Python来实现这些数据加密方式。

二、Python中实现数据加密的模块简介


Python中的大部分功能都是通过模块提供的,因此我们主要是通过Python中提供的一些内置的模块或外部模块来实现上面提到的各种加密算法。使用过程也很简单,只需要调用这些模块提供的相应的函数接口即可。

1. Python内置的加密模块演化过程

上面我们已经提到过,单向加密算法有:MD5、SHA系列算法 和 HMAC,而到目前为止Python内置的用于实现数据加密的模块也主要是提供单向加密功能,并且这些模块随着Python版本的迭代也经历了一些调整和整合:

  • Python2.5之前的版本所提供的加密模块有:md5、sha和hmac
  • Python2.5开始把对md5和sha算法的实现整合到一个新的模块:hashlib;
  • Python3.x开始去掉了md5和sha模块,仅剩下hashlib和hmac模块;
  • Python3.6增加了一个新的可以产生用于密钥管理的安全随机数的模块:secrets。

md5模块和sha模块为什么会被整合到一个hashlib模块中呢? 因为md5模块提供的是MD5算法的实现,sha模块提供的是SHA1算法的实现,而MD5和SHA1都是hash算法,具体解释看下面的名词解释。

2. 相关名词解释

  • HASH: 一般翻译为“散列”(也有直接音译为“哈希”),就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变成固定长度的输出,该输出值就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一确认输入值。简单来说,hash算法就是一种将任意长度的消息压缩为某一固定长度的消息摘要的函数

  • MD5: 全称为 Message Digest algorithm 5,即信息摘要算法。该算法可以生成定长的数据指纹,被广泛应用于加密和解密技术,常用于文件和数据的完整性校验。

  • SHA: 全称为 Secure Hash Algorithm,即安全散列算法/安全哈希算法。该算法是数字签名等密码学应用中的重要工具,被广泛应用于电子商务等信息安全领域。根据生成的密文的长度而命名的各种具体的算法有:SHA1(160bits)、SHA224(224bits)、SHA256(256bits)、SHA384(384bits)等。

  • HMAC: 全称为 Hash Message Authentication Code,即散列消息鉴别码。HMAC是基于密钥的哈希算法认证协议,主要是利用哈希算法(如MD5, SHA1),以一个密钥和一个消息作为输入,生成一个消息摘要作为输出,因此其具体的算法名称为HMAC-MD5、HMAC-SHA1等。可见HMAC算法是基于各种哈希算法的,只是它在运算过程中还可以使用一个密钥来增强安全性。

3. 本文要讲解的Python内置模块简介

Python早期的相关模块这里不再介绍了,我们今天主要说明的是以下几个模块:

模块名 内置模块 描述
hashlib Y 主要提供了一些常见的单向加密算法(如MD5,SHA等),每种算法都提供了与其同名的函数实现。
hmac Y 提供了hmac算法的实现,hamc也是单向加密算法,但是它支持设置一个额外的密钥(通常被称为'salt')来提高安全性
random Y 该模块主要用于一些随机操作,如获取一个随机数,从一个可迭代对象中随机获取指定个数的元素。
secrets Y 这是Python 3.6中新增的模块,用于获取安全随机数。
base64 Y 该模块主要用于二进制数据与可打印ASCII字符之间的转换操作,它提供了基于Base16, Base32, 和Base64算法以及实际标准Ascii85和Base85的编码和解码函数。
pycrypto N 支持单向加密、对称加密和公钥加密以及随机数操作,这是个第三方模块,需要额外安装。

说明: random模块严格上来讲并不能被称为数据加密模块,且官方文档中也强调过该模块不应该用于数据加密。但是我们在进行数据加密时,还是会常常用到随机数的操作,所以这里就顺便对这个模块进行下说明。

三、hashlib与hmac模块介绍


hashlib和hmac都是python内置的加密模块,它们都提供实现了单向加密算法的api。

1. hashlib模块

hashlib模块简介:

hashlib模块为不同的安全哈希/安全散列(Secure Hash Algorithm)和 信息摘要算法(Message Digest Algorithm)实现了一个公共的、通用的接口,也可以说是一个统一的入口。因为hashlib模块不仅仅是整合了md5和sha模块的功能,还提供了对更多中算法的函数实现,如:MD5,SHA1,SHA224,SHA256,SHA384和SHA512。

提示: “安全哈希/安全散列” 与 “信息摘要” 这两个术语是可以等价互换的。比较老的算法被称为消息摘要,而现代属于都是安全哈希/安全散列。

hashlib模块包含的函数与属性:

函数名/属性名 描述
hashlib.new(name[, data]) 这是一个通用的哈希对象构造函数,用于构造指定的哈希算法所对应的哈希对象。其中name参数用于指定哈希算法名称,如'md5', 'sha1',不区分大小写;data是一个可选参数,表示初始数据。
hashlib.哈希算法名称() 这是一个hashlib.new()的替换方式,可以直接通过具体的哈希算法名称对应的函数来获取哈希对象,如 hashlib.md5(),hashlib.sha1()等。
hashlib.algorithms_guaranteed Python 3.2新增的属性,它的值是一个该模块在所有平台都会支持的哈希算法的名称集合:set(['sha1', 'sha224', 'sha384', 'sha256', 'sha512', 'md5'])
hashlib.algorithms_available Python 3.2新增的属性,它的值是是一个当前运行的Python解释器中可用的哈希算法的名称集合,algorithms_guaranteed将永远是它的子集。

hash对象包含的方法与属性:

函数名/属性名 描述
hash.update(data) 更新哈希对象所要计算的数据,多次调用为累加效果,如m.update(a); m.update(b)等价于m.update(a+b)
hash.digest() 返回传递给update()函数的所有数据的摘要信息--二进制格式的字符串
hash.hexdigest() 返回传递给update()函数的所有数据的摘要信息--十六进制格式的字符串
hash.copy() 返回该哈希对象的一个copy("clone"),这个函数可以用来有效的计算共享一个公共初始子串的数据的摘要信息。
hash.digest_size hash结果的字节大小,即hash.digest()方法返回结果的字符串长度。这个属性的值对于一个哈希对象来说是固定的,md5:16,sha1(20), sha224(28)
hash.block_size hash算法内部块的字节大小
hash.name 当前hash对象对应的哈希算法的标准名称--小写形式,可以直接传递给hashlib.new()函数来创建另外一个同类型的哈希对象。
hashlib模块使用步骤:
  • 1)获取一个哈希算法对应的哈希对象(比如名称为hash): 可以通过 hashlib.new(哈希算法名称, 初始出入信息)函数,来获取这个哈希对象,如hashlib.new('MD5', 'Hello'),hashlib.new('SHA1', 'Hello')等;也可以通过hashlib.哈希算法名称()来获取这个哈希对象,如hashlib.md5(), hashlib.sha1()等。

  • 2)设置/追加输入信息: 调用已得到哈希对象的update(输入信息)方法可以设置或追加输入信息,多次调用该方法,等价于把每次传递的参数凭借后进行作为一个参数垫底给update()方法。也就是说,多次调用是累加,而不是覆盖。

  • 3)获取输入信息对应的摘要: 调用已得到的哈希对象的digest()方法或hexdigest()方法即可得到传递给update()方法的字符串参数的摘要信息。digest()方法返回的摘要信息是一个二进制格式的字符串,其中可能包含非ASCII字符,包括NUL字节,该字符串长度可以通过哈希对象的digest_size属性获取;而hexdigest()方法返回的摘要信息是一个16进制格式的字符串,该字符串中只包含16进制的数字,且长度是digest()返回结果长度的2倍,这可用邮件的安全交互或其它非二进制的环境中。

hashlib模块使用实例:

我们以MD5算法为例获取字符串"Hello, World"的摘要信息(也叫数据指纹)

import hashlib

hash  = hashlib.md5()
hash.update('Hello, ')
hash.update('World!')
ret1 = hash.digest()
print(type(ret1), len(ret1), ret1)
ret2 = hash.hexdigest()
print(type(ret2), len(ret2), ret2)

输出结果:

(<type 'str'>, 16, 'e\xa8\xe2}\x88y(81\xb6d\xbd\x8b\x7f\n\xd4')
(<type 'str'>, 32, '65a8e27d8879283831b664bd8b7f0ad4')

分析:

  • digest()方法返回的结果是一个二进制格式的字符串,字符串中的每个元素是一个字节,我们知道1个字节是8bits,MD5算法获取的数据摘要长度是128bits,因此最后得到的字符串长度是128/8=16;
  • hexdigest()方法返回的结果是一个16进制格式的字符串,字符串中每个元素是一个16进制数字,我们知道每个16进制数字占4bits,MD5算法获取的数据摘要长度是128bits,因此最后得到的字符串长度是128/4=32。

在实际工作中,我们通常都是获取数据指纹的16进制格式,比如我们在数据库中存放用户密码时,不是明文存放的,而是存放密码的16进制格式的摘要信息。当用户发起登录请求时,我们按照相同的哈希算法获取用户发送的密码的摘要信息,与数据中存放的与该账号对应的密码摘要信息做比对,两者一致则验证成功。

另外需要说明的是,下面这几段代码是等价的:

hash  = hashlib.md5()
hash.update('Hello, ')
hash.update('World!')
hash  = hashlib.md5()
hash.update('Hello, World!')
hash  = hashlib.new('md5')
hash.update('Hello, World!')
hash  = hashlib.new('md5', 'Hello, ')
hash.update('World!')

说明: 如果我们想使用其他哈希算法来获取数据指纹,只需要把上面代码中的"md5"换成其他算法名即可,如hashlib.sha1(),hashlib.new('sha1')等。

2. hmac模块

hashmac模块简介:

前面说过,HMAC算法也是一种一种单项加密算法,并且它是基于上面各种哈希算法/散列算法的,只是它可以在运算过程中使用一个密钥来增增强安全性。hmac模块实现了HAMC算法,提供了相应的函数和方法,且与hashlib提供的api基本一致。

hmac模块提供的函数:

函数名 描述
hmac.new(key, msg=None, digestmod=None) 用于创建一个hmac对象,key为密钥,msg为初始数据,digestmod为所使用的哈希算法,默认为hashlib.md5
hmac.compare_digest(a, b) 比较两个hmac对象,返回的是a==b的值

hmac对象中的方法和属性:

方法名/属性名 描述
HMAC.update(msg) 同hashlib.update(msg)
HMAC.digest() 同hashlib.digest()
HMAC.hexdigest() 同hashlib.hexdigest()
HMAC.copy() 同hashlib.copy()
HMAC.digest_size 同hashlib.digest_size
HMAC.block_size 同hashlib.block_size
HMAC.name 同hashlib.name
hmac模块使用步骤:

hmac模块模块的使用步骤与hashlib模块的使用步骤基本一致,只是在第1步获取hmac对象时,只能使用hmac.new()函数,因为hmac模块没有提供与具体哈希算法对应的函数来获取hmac对象。

hmac模块使用实例:
import hmac
import hashlib h1 = hmac.new('key', 'Hello, ')
h1.update('World!')
ret1 = h1.hexdigest()
print(type(ret1), len(ret1), ret1) h2 = hmac.new('key', digestmod=hashlib.md5)
h2.update('Hello, World!')
ret2 = h2.hexdigest()
print(type(ret2), len(ret2), ret2)

输出结果:

(<type 'str'>, 32, 'cfad9d610c1e548a03562f8eac399033')
(<type 'str'>, 32, 'cfad9d610c1e548a03562f8eac399033')

四、random与secrets模块介绍


random和secrets模块都是Pytthon内置的随机数操作模块,其中secrets模块是Python 3.6才新增的模块。

1. random模块

random模块介绍:

random模块实现了一个伪随机数生成器,可用来生成随机数以及完成与随机数相关的功能。下面我们来介绍下该模块下常用的几个函数:

函数名 描述
random.random() 用于生成半开区间[0, 1.0)内的一个随机浮点数
random.uniform(a, b) 用于生成一个指定范文内[a, b]的随机浮点数
random.randint(a, b) 用于生成一个指定范围内[a, b]的整数
random.randrange(start, stop[, step]) 用于从指定范围内[start, stop),按指定基数step递增的集合中获取一个随机数,step默认值为1。
random.randrange(stop) 等价于random.randrange(0, stop)
random.choice(seq) 从指定序列seq中随机获取一个元素
random.sample(population, k) 从指定序列中随机获取k个不重复的元素,并以列表形式返回,用于不进行内容替换的随机抽样。
random.shuffle(x[, random]) 用于随机打乱一个列表中元素,需要注意的是该函数操作的是列表对象,且没有返回值。

说明:

  • 1)random.sample(population, k)只有在population中没有重复元素的情况下获取到的随机抽样结果才会有相同的元素;
  • 2)其实上面这些函数都是基于random.random()这个基础函数实现的;
  • 3)官方文章中有声明,该模块完全不适合用作数据加密。
random模块实例:
import random

print("random.random(): ", random.random())
print("random.uniform(10, 20): ", random.uniform(10, 20))
print("random.randint(10, 20): ", random.randint(10, 20))
print("random.randrange(10, 20, 2): ", random.randrange(10, 20, 2))
print("random.choice('abcd1234'): ", random.choice('abcd1234'))
print("random.sample('abcd1234', 3): ", random.sample('abcd1234', 3))
print("random.sample('abcd1234', 3): ", random.sample('abcd1234', 3))
print("random.shuffle([1, 2, 3, 4, 5, 6]): ", random.shuffle([1, 2, 3, 4, 5, 6]))
list = [1, 2, 3, 4, 5, 6]
random.shuffle(list)
print("random.shuffle([1, 2, 3, 4, 5, 6]): ", list)

输出结果:

('random.random(): ', 0.2967959697940342)
('random.uniform(10, 20): ', 10.774070602657055)
('random.randint(10, 20): ', 18)
('random.randrange(10, 20, 2): ', 18)
("random.choice('abcd1234'): ", 'd')
("random.sample('abcd1234', 3): ", ['d', '1', '4'])
("random.sample('abcd1234', 3): ", ['d', 'd', 'd'])
('random.shuffle([1, 2, 3, 4, 5, 6]): ', None)
('random.shuffle([1, 2, 3, 4, 5, 6]): ', [5, 1, 2, 4, 3, 6])

再次说明,random.shuffle()的函数没有返回结果,且其操作的参数必须是一个列表对象。

2. secrets模块

secrets模块介绍:

secrets模块是Python 3.6新增的内置模块,它可以生成用于管理密码、账户验证信息、安全令牌和相关秘密信息等数据的密码强随机数。需要特别声明的是,与random模块中的默认伪随机数生成器相比,我们应该优先使用secrets模块,因为random模块中的默认伪随机数生成器是为建模和模拟而设计的,不是为安全或密码学而设计的。总体来讲,我们可以通过secrets模块完成两种操作:

  • 1)生成安全随机数
  • 2)生成一个笃定长度的随机字符串--可用作令牌和安全URL
secrets模块提供的函数:

下面来看下secrets模块提供的函数:

函数名 描述
secrets.choice(sequence) 功能与random.choice(seq)相同,从指定的非空序列中随机选择一个元素并返回
secrets.randbelow(n) 功能与random.randrange(n)相同,从半开区间[0, n)内随机返回一个整数
secrets.randbits(k) 返回一个带有k个随机位的整数
secrets.token_bytes([nbytes=None]) 返回一个包含nbytes个字节的随机字节串
secrets.token_hex([nbytes=None]) 返回一个包含nbytes字节的16进制格式的随机文本字符串,每个字节被转成成2个16进制数字,这可以用来生成一个随机密码
secrets.token_urlsafe([nbytes]) 返回一个包含nbytes个字节的随机安全URL文本字符串,这可以在提供重置密码的应用中用来生成一个临时的随机令牌
secrets.compare_digest(a, b) 比较字符串a和字符串b是否相等,相等则返回True,否则返回False

如果以上函数中的nbytes参数未提供或设置为None则会取一个合理的默认值。那么生成一个令牌(token)时应该使用多个字节呢? 为了抵抗暴力破解攻击,令牌需要有足够的随机性,当生成令牌使用的字节数越多时,暴力破解需要尝试的次数就越多。因此,当计算机的计算能力变得更强时,也就意味着计算可以再更短的时间内完成更多的猜测次数。因此,这里所使用的字节个数不应该是一个固定的值,而是应该随着计算机计算能力的增强而增加。到2015年为止,我们相信32个字节(256bits)的随机性对于secrets模块的典型应用场景来说是足够的了。

secrets模块的最佳实践

实例1: 生成一个由8位数字和字母组成的随机密码

import secrets
import string alphanum = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphanum) for i in range(8))

实例2: 生成一个由10位数字和字母组成的随机密码,要求至少有一个小写字符,至少一个大写字符 和 至少3个数字

import secrets
import string alphanum = string.ascii_letters + string.digits
while True:
password = ''.join(secrets.choice(alphanum) for i in range(10))
if (any(c.islower() for c in password)
and any(c.isupper() for c in password)
and len(c.isdigit() for c in password) >= 3):
break

实例3: 生成一个用于找回密码应用场景的、包含一个安全令牌的、很难猜到的临时URL

import secrets
url = 'https://mydomain.com/reset=' + secrets.token_urlsafe()

说明: secrets模块是Python 3.6新增的内置模块,尽管官方文档中强调过random模块并不适合用来做安全和加密相关的工作,但是在Python 3.6之前我们还是可以用random模块来模拟secrets模块提供的这些功能的实现。

五、base64模块介绍


经常听到有人说“base64加密”,其实base64并不能用于数据加密,它也不是为了纯粹的数据加密而生的,它的出现是为了解决不可见字符串的网络传输和数据保存问题。因为,用base64对数据进行转换的过程不能成为“加密”与“解密”,只能成为“编码”与“解码”。下面我们也会用到它,所以这里顺便做下简单的介绍。

1. base64的作用

Base64是一种用64个字符来表示任意二进制数据的方法,它是一种通过查表对二进制数据进行编码的方法,不能用于数据加密。base64最初的出现时为了能够正确的传输邮件数据,因为邮件中的附件(比如图片)的二进制数中可能存在不可见字符(ascii码中128-255之间的值是不可见字符),比如我们尝试用记事本或其他文本编辑器打开一个图片时,通常都会看到一大堆乱码,这些乱码就是不可见字符。由于早期的一些网络设备和网络协议是无法正确识别这些字符的,这就可能在数据传输时出现各种无法预知的问题。base64的作用就是把含有不可见字符的信息用可见字符来表示(Ascii码中0-127之间的值是可见字符),从而解决这个问题。

关于base64的介绍及实现原理可以看看这几篇文章:

2. base64的常见应用场景

base64适用于小段内容的编码,比如数字证书的签名、URL、Cookie信息等需要通过网络进行传输的小段数据。关于base64在数字签名中的应用会在本文后面讲解pycrypto模块使用实例时有具体的应用示例。

3. base64模块介绍及简单使用示例

Python中有一个内置的base64模块可直接用来进行base64的编码和解码工作--即提供 “二进制数据” 与 “可打印(可见)的ASCII字符”之间的转换功能。常用的函数有以下几个:

函数名 描述
base64.b64encode(s, altchars=None) 对二进制数据(字节串)s通过base64进行编码,返回编码后的字节串
base64.b64decode(s, altchars=None, validate=False) 对通过base64编码的字节对象或ASCII字符串s进行解码,返回解码后的字节串
base64.urlsafe_b64encode(s) 与b64encode()函数不同的是,它会把标准Base64编码结果中的字符'+'和字符'/'分别替换成字符'-'和字符'_'。
base64.urlsafe_b64decode(s) 解码通过base64.urlsafe_b64encode()函数编码的字节对象或ASCII字符串s。

提示: URL中有一些有特殊意义的字符,也就是保留字符:';', '/', '?', ':', '@', '&', '=', '+', '$' 和 ',' ,在URL的参数值中应该避免这些字符的出现。

下面来看个简单的示例:

import base64

data = 'hello, 世界!'
based_data1 = base64.b64encode(data)
plain_data1 = base64.b64decode(based_data1)
based_data2 = base64.urlsafe_b64encode(data)
plain_data2 = base64.urlsafe_b64decode(based_data2)
print(based_data1)
print(based_data2)
print(plain_data1)
print(plain_data2)

输出结果:

aGVsbG8sIOS4lueVjO+8gQ==
aGVsbG8sIOS4lueVjO-8gQ==
hello, 世界!
hello, 世界!

3. base64编码结果后的等号'='

通过上面的这个简单示例的输出结果会发现,随翻urlsafe_b64encode()函数会把编码结果中的字符'+'和字符'/'替换成其他URL的非保留字符,但是它的编码结果中还是可能出现字符'='。那么这些字符'='代表什么呢?能否去掉呢?

其实base64编码的过程中会先把原来数据中的每3个字节的二进制数据编码为4个字节的文本数据,当原始数据最后不满3个字节时就需要用'\00'字节进行补位凑够3个字节,而且会在编码结果的最后加上相应个数的'='号来表示补了多少个字节,这样解码的时候就可以去掉那些补位的字节。

由此我们可以得出两个结论:

  • 1)base64编码后的结果的末尾可能存在字符'='个数分别是:0个、1个和2个;
  • 2)base64编码后的结果应该是4的倍数。

基于以上第2个结论,为了避免编码结果中可能出现的的字符'='对网络数据传输造成影响,可以在传出前去掉后面的字符'=',接收方可以通过对数据的长度对4求模得到应该补上的字符'='个数,从而得到正确的数据。比如,我们可以通过下面这个解码函数来完成这个过程:

import base64

def safe_b64decode(s):
length = len(s) % 4
for i in range(length):
s = s + '='
return base64.b64decode(s) if __name__ == '__main__':
print(safe_b64decode('aGVsbG8sIOS4lueVjO+8gQ=='))
print(safe_b64decode('aGVsbG8sIOS4lueVjO+8gQ='))
print(safe_b64decode('aGVsbG8sIOS4lueVjO+8gQ'))

输出结果:

hello, 世界!
hello, 世界!
hello, 世界!

可见,虽然我们把上面那个示例中通过base64编码后的结果后面的字符'='去掉了,通过我们自己定义的safe_b64decode()函数最终得到了正确的解码结果。

六、pycrypto模块


1. pycryto模块介绍

pycryto模块不是Python的内置模块,它的官方网站地址是这里。pycrypto模块是一个实现了各种算法和协议的加密模块的结合,提供了各种加密方式对应的多种加密算法的实现,包括 单向加密、对称加密以及公钥加密和随机数操作。而上面介绍的hashlib和hmac虽然是Python的内置模块,但是它们只提供了单向加密相关算法的实现,如果要使用对称加密算法(如, DES,AES等)或者公钥加密算法我们通常都是使用pycryto这个第三方模块来实现。

需要注意的是,pycrypto模块最外层的包(package)不是pycrypto,而是Crypto。它根据加密方式类别的不同把各种加密方法的实现分别放到了不同的子包(sub packages)中,且每个加密算法都是以单独的Python模块(一个.py文件)存在的。我们来看下这些子包:

包名 描述
Crypto.Hash 该包中主要存放的是单向加密对应的各种哈希算法/散列算法的实现模块,如MD5.py, SHA.py,SHA256.py等。
Crypto.Cipher 该包中主要存放的是对称加密对应的各种加密算法的实现模块,如DES.py, AES.py, ARC4.py等;以及公钥加密对应的各种加密算法的实现模块,如PKCS1_v1_5.py等。
Crypto.PublicKey 该包中主要存放的是公钥加密与签名算法的实现模块,如RSA.py, DSA.py等。
Crypto.Signatue 该包中主要存放的是公钥签名相关算法的实现模块,如PKCS1_PSS.py, PKCS1_v1_5.py。
Crypto.Random 该包中只有一个随机数操作的实现模块 random.py
Crypto.Protocol 该包中存放的是一些加密协议的实现模块,如Chaffing.py, KDF.py等。
Crypto.Util 该包存放的是一些有用的模块和函数

这里需要说明的是,Crypto.PublicKey子包下的RSA.py和DSA.py模块只是用来生成秘钥对的,而基于公钥的加密与解密功能是由Crypto.Cipher子包下的PKCS1_v1_5.py或PKCS1_OAEP.py以这个密钥对儿为密钥来实现的;同样,签名与验证相关算法的功能是由Crypto.Signature子包下的PKCS1_v1_5.py和PKCS1_PASS.py以这个密钥对而为密钥来实现的。

2. pycrypto的安装与使用

pycrypto的安装

由于pycryto不是Python的内置模块,所以在使用它之前需要通过Python模块管理工具(如pip)来安装。不幸的是,如果你使用的是Windows平台会遇到一些问题,比如执行pip install pycryto命令来安装pycrpto时可能会得到以下错误提示信息:

error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat).

error: Unable to find vcvarsall.bat

这是由于pycrypto模块是用C语言实现的,Python模块管理工具在安装它时需要使用C/C++编译工具对它的代码进行编译,但是找不到对应版本的编译工具,具体解释及解决方案请查看《这篇博文》

pycrypto的使用方式

由于pycrypto把不同的类别加密算法的实现模块都放到了Crypto下不同的子包下了,所以我们只需要确定我们所需要使用的加密算法的实现模块在哪个子包下,然后导入相应的实现模块就可以使用了。比如我们打算使用MD5算法,就可以通过from Crypto.Hash import MD5来导入MD5这个模块,然后就可以使用该模块相应的api了。

pycrypto使用实例

实例1: 使用SHA256算法获取一段数据的摘要信息

from Crypto.Hash import SHA256

hash = SHA256.new()
hash.update('Hello, World!')
digest = hash.hexdigest()
print(digest)

输出结果:

dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

实例2: 使用AES算法加密,解密一段数据

from Crypto.Cipher import AES

# 加密与解密所使用的密钥,长度必须是16的倍数
secret_key = "ThisIs SecretKey"
# 要加密的明文数据,长度必须是16的倍数
plain_data = "Hello, World123!"
# IV参数,长度必须是16的倍数
iv_param = 'This is an IV456' # 数据加密
aes1 = AES.new(secret_key, AES.MODE_CBC, iv_param)
cipher_data = aes1.encrypt(plain_data)
print('cipher data:', cipher_data) # 数据解密
aes2 = AES.new(secret_key, AES.MODE_CBC, 'This is an IV456')
plain_data2 = aes2.decrypt(cipher_data) # 解密后的明文数据
print('plain text:', plain_data2)

输出结果:

('cipher data\xef\xbc\x9a', '\xcb\x7fd\x03\x12T,\xbe\x91\xac\x1a\xd5\xaa\xe6P\x9a')
('plain text\xef\xbc\x9a', 'Hello, World123!')

实例3: 随机数操作

from Crypto.Random import random

print('random.randint: ', random.randint(10, 20))
print('random.randrange: ', random.randrange(10, 20, 2))
print('random.randint: ', random.getrandbits(3))
print('random.choice: ', random.choice([1, 2, 3, 4, 5]))
print('random.sample: ', random.sample([1, 2, 3, 4, 5], 3))
list = [1, 2, 3, 4, 5]
random.shuffle(list)
print('random.shuffle: ', list)

输出结果:

('random.randint: ', 10L)
('random.randrange: ', 10L)
('random.randint: ', 5L)
('random.choice: ', 5)
('random.sample: ', [5, 4, 2])
('random.shuffle: ', [5, 2, 1, 3, 4])

实例4: 使用RSA算法生成密钥对儿

生成秘钥对:

from Crypto import Random
from Crypto.PublicKey import RSA # 获取一个伪随机数生成器
random_generator = Random.new().read
# 获取一个rsa算法对应的密钥对生成器实例
rsa = RSA.generate(1024, random_generator) # 生成私钥并保存
private_pem = rsa.exportKey()
with open('rsa.key', 'w') as f:
f.write(private_pem) # 生成公钥并保存
public_pem = rsa.publickey().exportKey()
with open('rsa.pub', 'w') as f:
f.write(public_pem)

私钥文件rsa.key的内容为:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCo7vV5xSzEdQeFq9n5MIWgIuLTBHuutZlFv+Ed8fIk3yC4So/d
y1f64iuYFcDeNU7eVGqTSkHmAl4AihDXoaH6hxohrcX0bCg0j+VoQMe2zID7MzcE
d50FhJbuG6JsWtYzLUYs7/cQ3urZYwB4PEVa0WxQj2aXUMsxp6vl1CgB4QIDAQAB
AoGAS/I5y4e4S43tVsvej6efu1FTtdhDHlUn1fKgawz1dlwVYqSqruSW5gQ94v6M
mZlPnqZGz3bHz3bq+cUYM0jH/5Tygz4a+dosziRCUbjMsFePbJ4nvGC/1hwQweCm
+7sxog4sw91FrOfAg/iCcoeho0DghDolH9+zzwRYPIWUyUECQQDFGe+qccGwL9cU
v+GmZxtF8GkRL7YrXI7cvnZhnZZ7TANjxlYukLGEpiFGIDd0Aky1QhkK18L8DTO4
+iGXTpgJAkEA22o03/1IqeRBofbkkDmndArHNUnmv5pyVFaLKPoVgA4A1YsvqxUL
DK6RwFGONUMknBWY59EDKCUdIf3CsVIhGQJAJKDMRB19xBMv4iBCe9z/WYDy1YnL
TcWWmvkeIMfbVjBrFNif3WlwQ9lnp5OHGpzuymRtKPGtv49ohECfi3HEmQJAPI+n
AoAdk07+Up8b3TccoinrbCj2uMH/dongpTHJx2uWDVr6kEUhpKF2d1fLYaYjr7VC
XBHTxjvgO6aYG2to2QJBAIzDugOSTeQFpidCoewfa0XX4guF+WRf8wzyBC/XE6TY
3cIY05sjbpfiVwW/Cb8Z2ia8EgBTGN8HSIFOUQ2jRl4=
-----END RSA PRIVATE KEY-----

公钥文件rsa.pub的内容为:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo7vV5xSzEdQeFq9n5MIWgIuLT
BHuutZlFv+Ed8fIk3yC4So/dy1f64iuYFcDeNU7eVGqTSkHmAl4AihDXoaH6hxoh
rcX0bCg0j+VoQMe2zID7MzcEd50FhJbuG6JsWtYzLUYs7/cQ3urZYwB4PEVa0WxQ
j2aXUMsxp6vl1CgB4QIDAQAB
-----END PUBLIC KEY-----

实例5: 公钥加密算法的实现

前面说过,公钥加密算法是由Crypto.Cipher子包下的PKCS1_v1_5.py或PKCS1_OAEP.py模块以已经存在的密钥对儿为密钥来实现的,现在常用的是PKCS1_v1_5。另外,我们前面提到过,使用对方的公钥加密,使用对方的私钥解密才能保证数据的机密性,因此这里以上面生成的公钥进行加密数据,以上面生成的私钥解密数据:

from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5
import base64 # 数据加密
message = "This is a plain text."
with open('rsa.pub', 'r') as f:
public_key = f.read()
rsa_key_obj = RSA.importKey(public_key)
cipher_obj = Cipher_PKCS1_v1_5.new(rsa_key_obj)
cipher_text = base64.b64encode(cipher_obj.encrypt(message))
print('cipher test: ', cipher_text) # 数据解密
with open('rsa.key', 'r') as f:
private_key = f.read()
rsa_key_obj = RSA.importKey(private_key)
cipher_obj = Cipher_PKCS1_v1_5.new(rsa_key_obj)
random_generator = Random.new().read
plain_text = cipher_obj.decrypt(base64.b64decode(cipher_text), random_generator)
print('plain text: ', plain_text)

输出结果:

('cipher test: ', 'oq1sOSz4lS9PgrKmiwuAHs7iUhmWMvWdEbXLTOdhGtyIAr6xwmjtnBNpuvMVIM2Mz/O/xVzPu5L8nzUVW2THKpQinNwC7JWF0wnxrTHwKrmfXIIxxibQJS02obxkoEeqrjRo0b8V7yktYIV3ig2SlU3yjcr+lOFmRX+h6dE2TAI=')
('plain text: ', 'This is a plain text.')

实例6: 数据签名与签名验证的实现

同样,签名与验证相关算法的功能是由Crypto.Signature子包下的PKCS1_v1_5.py和PKCS1_PASS.py以这个密钥对而为密钥来实现的。数据签名的目的是为了防止别人篡改发送人的原始数据,其原理是:

  • 1)先以单向加密方式通过某种哈希算法(如MD5,SHA1等)对要发送的数据生成摘要信息(数据指纹);
  • 2)然后发送方用自己密钥对儿中的私钥对这个摘要信息进行加密;
  • 3)数据接收方用发送的公钥对加密后的摘要信息进行解密,得到数据摘要的明文A;
  • 4)数据接收方再通过相同的哈希算法计算得到数据摘要信息B;
  • 5)数据接收方对比数据摘要A与数据摘要B,如果两者一致说明数据没有被篡改过。
from Crypto.Hash import SHA
from Crypto.Signature import PKCS1_v1_5 as Signature_PKCS1_v1_5
message = "This is the message to send."
# 数据签名
with open('rsa.key', 'r') as f:
private_key = f.read()
rsa_key_obj = RSA.importKey(private_key)
signer = Signature_PKCS1_v1_5.new(rsa_key_obj)
digest = SHA.new()
digest.update(message)
signature = base64.b64encode(signer.sign(digest))
print('signature text: ', signature) # 验证签名
with open('rsa.pub', 'r') as f:
public_key = f.read()
rsa_key_obj = RSA.importKey(public_key)
signer = Signature_PKCS1_v1_5.new(rsa_key_obj)
digest = SHA.new(message)
is_ok = signer.verify(digest, base64.b64decode(signature))
print('is ok: ', is_ok)

输出结果:

('signature text: ', 'Bb4gvPU9Ji63kk3SSTiAVLctDbdb91DQuQKecbTcO2Jvpwbr7fr9sKZO+vZ8LIuSOdJkhbGX6swsSNwDI/CoT0xCdjiasfySPgsLyTcSWLyy9P7SrDuveH1ABUR/oYisvT1wFsScu0NMOBR8sLpboPk2DiW6n400jZq7t09xUyc=')
('is ok: ', True)

上面这几个关于pycrpto的使用实例来自这里

七、总结


上面讲了很多内容,现在我们简单总结下:

  • 数据加密方式大体分为3类:单向加密、对称加密 和 公钥加密(非对称加密)。
  • 这3类加密方式都各自包含不同的加密算法,如单向加密方式中包含MD5、SHA1、SHA256等,这些算法又称为“哈希算法”或“散列算法”或“数据摘要算法”
  • Python内置的hashlib和hmac只提供了单向加密的各种算法实现,如果要做对称加密或者公钥加密操作需要安装第三方扩展模块,常用的是pycrypto模块。另外,hmac允许在使用哈希算法计算数据摘要时使用一个密钥。
  • 随机数操作可以通过三个模块来做,Python内置的random模块和secrets模块(Python 3.6中才可用),还可以通过pycrypto模块中的Crypto.Random子包中的模块来完成。
  • base64只适合编码小段数据,且不能用于数据加密(算法是公开的,且没有密钥,所有人都可以解码)
  • pycrypto是一个加密算法库,几乎所有的加密算法都可以在它里面找到相应的实现模块。

八、参考文档


问题交流群:666948590