Python 3 的標準函式庫中沒有多少用來解決加密的,不過卻有用來處理雜湊的函式庫。這裡我們會對其進行一個簡單的介紹,但重點會放在兩個第三方的軟體包:PyCrypto 和 cryptography 上。我們將學習如何使用這兩個函式庫,來加密和解密字串。
如果需要用到安全哈希演算法或是訊息摘要演算法,那麼你可以使用標準函式庫中的 hashlib 模組。這個模組包含了符合 FIPS(美國聯邦資訊處理標準)的安全雜湊演算法,包括 SHA1,SHA224,SHA256,SHA384,SHA512 以及 RSA 的 MD5 演算法。 Python 也支援 adler32 以及 crc32 雜湊函數,不過它們在 zlib 模組中。
雜湊的一個最常見的用法是,儲存密碼的雜湊值而非密碼本身。當然了,使用的雜湊函數需要穩健一點,否則容易被破解。另一個常見的用法是,計算一個檔案的雜湊值,然後將這個檔案和它的雜湊值分別發送。接收到檔案的人可以計算檔案的雜湊值,檢驗是否與接受到的雜湊值相符。如果兩者相符,就表示文件在傳送的過程中未經竄改。
讓我們試著建立一個 md5 雜湊:
>>> import hashlib >>> md5 = hashlib.md5() >>> md5.update('Python rocks!') Traceback (most recent call last): File "<pyshell#5>", line 1, in <module> md5.update('Python rocks!') TypeError: Unicode-objects must be encoded before hashing >>> md5.update(b'Python rocks!') >>> md5.digest() b'\x14\x82\xec\x1b#d\xf6N}\x16*+[\x16\xf4w'
讓我們花點時間一行一行來講解。首先,我們導入 hashlib ,然後建立一個 md5 哈希物件的實例。接著,我們在這個實例中加入一個字串後,卻得到了報錯訊息。原來,計算 md5 雜湊時,需要使用位元組形式的字串而非普通字串。正確添加字串後,我們呼叫它的 digest 函數來得到雜湊值。如果你想要十六進位的雜湊值,也可以用以下方法:
>>> md5.hexdigest() '1482ec1b2364f64e7d162a2b5b16f477'
實際上,有一個精簡的方法來建立哈希,下面我們看一下用這個方法建立一個sha1雜湊:
>>> sha = hashlib.sha1(b'Hello Python').hexdigest() >>> sha '422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'
可以看到,我們可以同時建立一個雜湊實例並且呼叫其digest 函數。然後,我們印出這個哈希值看一下。這裡我使用 sha1 雜湊函數作為例子,但它不是特別安全,讀者可以隨意嘗試其他的雜湊函數。
Python 的標準函式庫對金鑰導出支援較弱。實際上,hashlib 函數庫提供的唯一方法是 pbkdf2_hmac 函數。它是 PKCS#5 的第二個以口令為基礎的金鑰導出函數,並使用 HMAC 作為偽隨機函數。因為它支援「加鹽salt」和迭代操作,你可以使用類似的方法來哈希你的密碼。例如,如果你打算使用 SHA-256 加密方法,你將需要至少 16 個位元組的“鹽”,以及最少 100000 次的迭代操作。
簡單來說,「鹽」就是隨機的數據,被用來加入哈希的過程中,以加大破解的難度。這基本上可以保護你的密碼免受字典和彩虹表rainbow table的攻擊。
讓我們來看一個簡單的例子:
>>> import binascii >>> 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 的第三方函式庫,它是被專門設計出來雜湊密碼的。
PyCrypto 可能是 Python 中密碼學方面最有名的第三方軟體包。可惜的是,它的開發工作在 2012 年就已經停止。其他人還在繼續發布最新版本的 PyCrypto,如果你不介意使用第三方的二進位包,仍可以獲得 Python 3.5 的相應版本。例如,我在 Github (https://github.com/sfbahr/PyCrypto-Wheels) 上找到了對應 Python 3.5 的 PyCrypto 二進位套件。
幸運的是,有一個該專案的分支 PyCrytodome 取代了 PyCrypto 。為了在Linux 上安裝它,你可以使用以下pip 指令:
pip install pycryptodome
在Windows 系統上安裝則稍有不同:
pip install pycryptodomex
如果你遇到了問題,可能是因為你沒有安裝正確的依賴套件(LCTT 譯註:如python-devel),或是你的Windows 系統需要一個編譯器。
也值得注意的是,PyCryptodome 在 PyCrypto 最後版本的基礎上有許多改進。非常值得去訪問它們的主頁,看看有什麼新的功能。
訪問了他們的主頁之後,我們可以看一些例子。在第一個例子中,我們將使用 DES 演算法來加密一個字串:
>>> from Crypto.Cipher import DES >>> key = 'abcdefgh' >>> def pad(text): while len(text) % 8 != 0: text += ' ' return text >>> des = DES.new(key, DES.MODE_ECB) >>> text = 'Python rocks!' >>> padded_text = pad(text) >>> encrypted_text = des.encrypt(text) Traceback (most recent call last): File "<pyshell#35>", line 1, in <module> encrypted_text = des.encrypt(text) File "C:\Programs\Python\Python35-32\lib\site-packages\Crypto\Cipher\blockalgo.py", line 244, in encrypt return self._cipher.encrypt(plaintext) ValueError: Input strings must be a multiple of 8 in length >>> encrypted_text = des.encrypt(padded_text) >>> encrypted_text b'>\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'Python rocks! '
幸运的是,解密非常容易,我们只需要调用 des 对象的 decrypt 方法就可以得到我们原来的 byte 类型字符串了。下一个任务是学习如何用 RSA 算法加密和解密一个文件。首先,我们需要创建一些 RSA 密钥。
如果你希望使用 RSA 算法加密数据,那么你需要拥有访问 RAS 公钥和私钥的权限,否则你需要生成一组自己的密钥对。在这个例子中,我们将生成自己的密钥对。创建 RSA 密钥非常容易,所以我们将在 Python 解释器中完成。
>>> from Crypto.PublicKey import RSA >>> code = 'nooneknows' >>> key = RSA.generate(2048) >>> encrypted_key = key.exportKey(passphrase=code, pkcs=8, protection="scryptAndAES128-CBC") >>> with open('/path_to_private_key/my_private_rsa_key.bin', 'wb') as f: f.write(encrypted_key) >>> with open('/path_to_public_key/my_rsa_public.pem', 'wb') as f: f.write(key.publickey().exportKey())
首先我们从 Crypto.PublicKey 包中导入 RSA,然后创建一个傻傻的密码。接着我们生成 2048 位的 RSA 密钥。现在我们到了关键的部分。为了生成私钥,我们需要调用 RSA 密钥实例的 exportKey 方法,然后传入密码,使用的 PKCS 标准,以及加密方案这三个参数。之后,我们把私钥写入磁盘的文件中。
接下来,我们通过 RSA 密钥实例的 publickey 方法创建我们的公钥。我们使用方法链调用 publickey 和 exportKey 方法生成公钥,同样将它写入磁盘上的文件。
有了私钥和公钥之后,我们就可以加密一些数据,并写入文件了。这里有个比较标准的例子:
from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes from Crypto.Cipher import AES, PKCS1_OAEP with open('/path/to/encrypted_data.bin', 'wb') as out_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'blah blah blah Python blah blah' 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 个字节。随意用一个你喜欢的编辑器试着打开这个被加密的文件,你应该只能看到乱码。
现在让我们学习如何解密我们的数据。
from Crypto.PublicKey import RSA from Crypto.Cipher import AES, PKCS1_OAEP code = 'nooneknows' with open('/path/to/encrypted_data.bin', 'rb') as fobj: private_key = RSA.import_key( open('/path_to_private_key/my_rsa_key.pem').read(), passphrase=code) enc_session_key, nonce, tag, ciphertext = [ fobj.read(x) for x in (private_key.size_in_bytes(), 16, 16, -1) ] cipher_rsa = PKCS1_OAEP.new(private_key) session_key = cipher_rsa.decrypt(enc_session_key) cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce) data = cipher_aes.decrypt_and_verify(ciphertext, tag) print(data)
如果你认真看了上一个例子,这段代码应该很容易解析。在这里,我们先以二进制模式读取我们的加密文件,然后导入私钥。注意,当你导入私钥时,需要提供一个密码,否则会出现错误。然后,我们文件中读取数据,首先是加密的会话密钥,然后是 16 字节的随机数和 16 字节的消息认证码,最后是剩下的加密的数据。
接下来我们需要解密出会话密钥,重新创建 AES 密钥,然后解密出数据。
你还可以用 PyCryptodome 库做更多的事。不过我们要接着讨论在 Python 中还可以用什么来满足我们加密解密的需求。
cryptography 的目标是成为“人类易于使用的密码学包cryptography for humans”,就像 requests 是“人类易于使用的 HTTP 库HTTP for Humans”一样。这个想法使你能够创建简单安全、易于使用的加密方案。如果有需要的话,你也可以使用一些底层的密码学基元,但这也需要你知道更多的细节,否则创建的东西将是不安全的。
如果你使用的 Python 版本是 3.5, 你可以使用 pip 安装,如下:
pip install cryptography
你会看到 cryptography 包还安装了一些依赖包(LCTT 译注:如 libopenssl-devel)。如果安装都顺利,我们就可以试着加密一些文本了。让我们使用 Fernet 对称加密算法,它保证了你加密的任何信息在不知道密码的情况下不能被篡改或读取。Fernet 还通过 MultiFernet 支持密钥轮换。下面让我们看一个简单的例子:
>>> from cryptography.fernet import Fernet >>> cipher_key = Fernet.generate_key() >>> cipher_key b'APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o=' >>> cipher = Fernet(cipher_key) >>> text = b'My super secret message' >>> encrypted_text = cipher.encrypt(text) >>> encrypted_text (b'gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f' b'6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=') >>> decrypted_text = cipher.decrypt(encrypted_text) >>> decrypted_text b'My super secret message'
首先我们需要导入 Fernet,然后生成一个密钥。我们输出密钥看看它是什么样儿。如你所见,它是一个随机的字节串。如果你愿意的话,可以试着多运行 generate_key 方法几次,生成的密钥会是不同的。然后我们使用这个密钥生成 Fernet 密码实例。
现在我们有了用来加密和解密消息的密码。下一步是创建一个需要加密的消息,然后使用 encrypt 方法对它加密。我打印出加密的文本,然后你可以看到你再也读不懂它了。为了解密出我们的秘密消息,我们只需调用 decrypt 方法,并传入加密的文本作为参数。结果就是我们得到了消息字节串形式的纯文本。
这一章仅仅浅显地介绍了 PyCryptodome 和 cryptography 这两个包的使用。不过这也确实给了你一个关于如何加密解密字符串和文件的简述。请务必阅读文档,做做实验,看看还能做些什么!
以上是Python3中加密與解密詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!