Python3:加密简介

python 3 的标准库中没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:pycrypto 和 cryptography 上。我们将学习如何使用这两个库,来加密和解密字符串。
哈希
如果需要用到安全哈希算法或是消息摘要算法,那么你可以使用标准库中的hashlib模块。这个模块包含了符合 fips(美国联邦信息处理标准)的安全哈希算法,包括 sha1,sha224,sha256,sha384,sha512 以及 rsa 的 md5 算法。python 也支持 adler32 以及 crc32 哈希函数,不过它们在zlib模块中。
哈希的一个最常见的用法是,存储密码的哈希值而非密码本身。当然了,使用的哈希函数需要稳健一点,否则容易被破解。另一个常见的用法是,计算一个文件的哈希值,然后将这个文件和它的哈希值分别发送。接收到文件的人可以计算文件的哈希值,检验是否与接受到的哈希值相符。如果两者相符,就说明文件在传送的过程中未经篡改。
让我们试着创建一个 md5 哈希:
>>>importhashlib>>>md5=hashlib.md5()>>>md5.update('pythonrocks!')traceback(mostrecentcalllast):file"<pyshell#5>",line1,in<module>md5.update('pythonrocks!')typeerror:unicode-objectsmustbeencodedbeforehashing>>>md5.update(b'pythonrocks!')>>>md5.digest()b'\x14\x82\xec\x1b#d\xf6n}\x16*+[\x16\xf4w'
让我们花点时间一行一行来讲解。首先,我们导入hashlib,然后创建一个 md5 哈希对象的实例。接着,我们向这个实例中添加一个字符串后,却得到了报错信息。原来,计算 md5 哈希时,需要使用字节形式的字符串而非普通字符串。正确添加字符串后,我们调用它的digest函数来得到哈希值。如果你想要十六进制的哈希值,也可以用以下方法:
>>>md5.hexdigest()'1482ec1b2364f64e7d162a2b5b16f477'
实际上,有一种精简的方法来创建哈希,下面我们看一下用这种方法创建一个 sha1 哈希:
>>>sha=hashlib.sha1(b'hellopython').hexdigest()>>>sha'422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'
可以看到,我们可以同时创建一个哈希实例并且调用其 digest 函数。然后,我们打印出这个哈希值看一下。这里我使用 sha1 哈希函数作为例子,但它不是特别安全,读者可以随意尝试其他的哈希函数。
密钥导出
python 的标准库对密钥导出支持较弱。实际上,hashlib 函数库提供的唯一方法就是pbkdf2_hmac函数。它是 pkcs#5 的基于口令的第二个密钥导出函数,并使用 hmac 作为伪随机函数。因为它支持“加盐salt”和迭代操作,你可以使用类似的方法来哈希你的密码。例如,如果你打算使用 sha-256 加密方法,你将需要至少 16 个字节的“盐”,以及最少 100000 次的迭代操作。
简单来说,“盐”就是随机的数据,被用来加入到哈希的过程中,以加大破解的难度。这基本可以保护你的密码免受字典和彩虹表rainbow table的攻击。
让我们看一个简单的例子:
>>>importbinascii>>>dk=hashlib.pbkdf2_hmac(hash_name='sha256',password=b'bad_password34',salt=b'bad_salt',iterations=100000)>>>binascii.hexlify(dk)b'6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02'
这里,我们用 sha256 对一个密码进行哈希,使用了一个糟糕的盐,但经过了 100000 次迭代操作。当然,sha 实际上并不被推荐用来创建密码的密钥。你应该使用类似scrypt的算法来替代。另一个不错的选择是使用一个叫bcrypt的第三方库,它是被专门设计出来哈希密码的。
pycryptodome
pycrypto 可能是 python 中密码学方面最有名的第三方软件包。可惜的是,它的开发工作于 2012 年就已停止。其他人还在继续发布最新版本的 pycrypto,如果你不介意使用第三方的二进制包,仍可以取得 python 3.5 的相应版本。比如,我在 github (https://github/sfbahr/pycrypto-wheels) 上找到了对应 python 3.5 的 pycrypto 二进制包。
幸运的是,有一个该项目的分支 pycrytodome 取代了 pycrypto 。为了在 linux 上安装它,你可以使用以下 pip 命令:
pipinstallpycryptodome
在 windows 系统上安装则稍有不同:
pipinstallpycryptodomex
如果你遇到了问题,可能是因为你没有安装正确的依赖包(lctt 译注:如 python-devel),或者你的 windows 系统需要一个编译器。如果你需要安装上的帮助或技术支持,可以访问 pycryptodome 的网站[1]。
还值得注意的是,pycryptodome 在 pycrypto 最后版本的基础上有很多改进。非常值得去访问它们的主页,看看有什么新的特性。
加密字符串
访问了他们的主页之后,我们可以看一些例子。在第一个例子中,我们将使用 des 算法来加密一个字符串:
>>>fromcrypto.cipherimportdes>>>key='abcdefgh'>>>defpad(text):whilelen(text)%8!=0:text+=''returntext>>>des=des.new(key,des.mode_ecb)>>>text='pythonrocks!'>>>padded_text=pad(text)>>>encrypted_text=des.encrypt(text)traceback(mostrecentcalllast):file"<pyshell#35>",line1,in<module>encrypted_text=des.encrypt(text)file"c:\programs\python\python35-32\lib\site-packages\crypto\cipher\blockalgo.py",line244,inencryptreturnself._cipher.encrypt(plaintext)valueerror:inputstringsmustbeamultipleof8inlength>>>encrypted_text=des.encrypt(padded_text)>>>encrypted_textb'>\xfc\x1f\x16x\x87\xb2\x93\x0e\xfch\x02\xd59vq'
这段代码稍有些复杂,让我们一点点来看。首先需要注意的是,des 加密使用的密钥长度为 8 个字节,这也是我们将密钥变量设置为 8 个字符的原因。而我们需要加密的字符串的长度必须是 8 的倍数,所以我们创建了一个名为pad的函数,来给一个字符串末尾填充空格,直到它的长度是 8 的倍数。然后,我们创建了一个 des 的实例,以及我们需要加密的文本。我们还创建了一个经过填充处理的文本。我们尝试着对未经填充处理的文本进行加密,啊欧,报了一个 valueerror 错误!我们需要对经过填充处理的文本进行加密,然后得到加密的字符串。(lctt 译注:encrypt 函数的参数应为 byte 类型字符串,代码为:encrypted_text = des.encrypt(padded_text.encode('utf-8')))
知道了如何加密,还要知道如何解密:
>>>des.decrypt(encrypted_text)b'pythonrocks!'
幸运的是,解密非常容易,我们只需要调用 des 对象的decrypt方法就可以得到我们原来的 byte 类型字符串了。下一个任务是学习如何用 rsa 算法加密和解密一个文件。首先,我们需要创建一些 rsa 密钥。
创建 rsa 密钥
如果你希望使用 rsa 算法加密数据,那么你需要拥有访问 ras 公钥和私钥的权限,否则你需要生成一组自己的密钥对。在这个例子中,我们将生成自己的密钥对。创建 rsa 密钥非常容易,所以我们将在 python 解释器中完成。
>>>fromcrypto.publickeyimportrsa>>>code='nooneknows'>>>key=rsa.generate(2048)>>>encrypted_key=key.exportkey(passphrase=code,pkcs=8,protection="scryptandaes128-cbc")>>>withopen('/path_to_private_key/my_private_rsa_key.bin','wb')asf:f.write(encrypted_key)>>>withopen('/path_to_public_key/my_rsa_public.pem','wb')asf:f.write(key.publickey().exportkey())
首先我们从crypto.publickey包中导入rsa,然后创建一个傻傻的密码。接着我们生成 2048 位的 rsa 密钥。现在我们到了关键的部分。为了生成私钥,我们需要调用 rsa 密钥实例的exportkey方法,然后传入密码,使用的 pkcs 标准,以及加密方案这三个参数。之后,我们把私钥写入磁盘的文件中。
接下来,我们通过 rsa 密钥实例的publickey方法创建我们的公钥。我们使用方法链调用 publickey 和 exportkey 方法生成公钥,同样将它写入磁盘上的文件。
加密文件
有了私钥和公钥之后,我们就可以加密一些数据,并写入文件了。这里有个比较标准的例子:
fromcrypto.publickeyimportrsafromcrypto.randomimportget_random_bytesfromcrypto.cipherimportaes,pkcs1_oaepwithopen('/path/to/encrypted_data.bin','wb')asout_file:recipient_key=rsa.import_key(open('/path_to_public_key/my_rsa_public.pem').read())session_key=get_random_bytes(16)cipher_rsa=pkcs1_oaep.new(recipient_key)out_file.write(cipher_rsa.encrypt(session_key))cipher_aes=aes.new(session_key,aes.mode_eax)data=b'blahblahblahpythonblahblah'ciphertext,tag=cipher_aes.encrypt_and_digest(data)out_file.write(cipher_aes.nonce)out_file.write(tag)out_file.write(ciphertext)
代码的前三行导入 pycryptodome 包。然后我们打开一个文件用于写入数据。接着我们导入公钥赋给一个变量,创建一个 16 字节的会话密钥。在这个例子中,我们将使用混合加密方法,即 pkcs#1 oaep ,也就是最优非对称加密填充。这允许我们向文件中写入任意长度的数据。接着我们创建 aes 加密,要加密的数据,然后加密数据。我们将得到加密的文本和消息认证码。最后,我们将随机数,消息认证码和加密的文本写入文件。
顺便提一下,随机数通常是真随机或伪随机数,只是用来进行密码通信的。对于 aes 加密,其密钥长度最少是 16 个字节。随意用一个你喜欢的编辑器试着打开这个被加密的文件,你应该只能看到乱码。
现在让我们学习如何解密我们的数据。
fromcrypto.publickeyimportrsafromcrypto.cipherimportaes,pkcs1_oaepcode='nooneknows'withopen('/path/to/encrypted_data.bin','rb')asfobj:private_key=rsa.import_key(open('/path_to_private_key/my_rsa_key.pem').read(),passphrase=code) ...