介紹如何在 Python 中使用 PyCryptodome
模組以 AES 對稱式加密方法對資料進行加密與解密。
AES 進階加密標準由美國國家標準與技術研究院(NIST)於 2001 年 11 月 26 日發布於 FIPS PUB 197,並在 2002 年 5 月 26 日成為有效的標準,是目前主流的對稱金鑰加密演算法之一。
以下將介紹如何在 Python 中安裝並使用 PyCryptodome
模組,以 AES 加密方法對資料進行加密與解密。
PyCryptodome
模組使用 pip 安裝 Python 的 PyCryptodome
模組:
# 安裝 PyCryptodome 模組
sudo pip3 install pycryptodome
這種安裝方式會讓 PyCryptodome
安裝在 Crypto
套件路徑之下,取代舊的 PyCrypto
模組,這兩個模組會互相干擾,所以不可以同時安裝。
測試 PyCryptodome
模組是否可以正常運作:
# 測試 PyCryptodome 模組 python3 -m Crypto.SelfTest
如果不希望影響到舊的 PyCrypto
模組,也可以選擇以獨立模組的方式安裝,將 PyCryptodome
模組安裝至 Cryptodome
套件路徑之下:
# 安裝 PyCryptodome 模組 sudo pip3 install pycryptodomex # 測試 PyCryptodome 模組 python -m Cryptodome.SelfTest
AES 加密方式的區塊長度固定為 128 位元,而金鑰長度則可以是 128、192 或 256 位元,在用 AES 進行資料加密之前,亦須先建立一組金鑰,最簡單的方式就是以隨機的方式產生金鑰。
from Crypto.Random import get_random_bytes # 產生 256 位元隨機金鑰(32 位元組 = 256 位元) key = get_random_bytes(32) print(key)
b'l\n\xe8\x7f#\xec{\xf9\x8a4\xb8hye\xe9V\\\xfb\x01\x08\x854\x89\xc9\xfc\x80\xa2S\x920@}'
如果希望使用一般的密碼來對資料進行加密與解密,可以根據密碼與一串固定的 salt 字串,產生對應的金鑰。
首先以亂數方式產生一串隨機的資料作為固定的 salt:
# 產生 salt print(get_random_bytes(32))
b'\xd0\x18\xa7QM\xd6\x9b\xebxu\xe4\xed\xa8\x83\xf6\xa3/\x01\x9c\x9e\x86n\xda;\x10EdD\xf7\x932\xcc'
為了方便起見,可以將這串 salt 直接寫在程式當中,搭配自己的密碼即可產生金鑰:
from Crypto.Protocol.KDF import PBKDF2 # 固定的 salt salt = b'\xd0\x18\xa7QM\xd6\x9b\xebxu\xe4\xed\xa8\x83\xf6\xa3/\x01\x9c\x9e\x86n\xda;\x10EdD\xf7\x932\xcc' # 密碼 password = 'my#password' # 根據密碼與 salt 產生金鑰 key = PBKDF2(password, salt, dkLen=32)
實務上我們通常會將產生的金鑰儲存在檔案中,方便後續的加密與解密使用:
# 金鑰儲存位置 keyPath = "my_key.bin" # 儲存金鑰 with open(keyPath, "wb") as f: f.write(key) # 讀取金鑰 with open(keyPath, "rb") as f: keyFromFile = f.read() # 檢查金鑰儲存 assert key == keyFromFile, '金鑰不符'
以下是使用 AES 的 CBC 模式對資料進行加密的範例,以 CBC 模式加密時需要先對資料進行 padding 處理,再進行加密。
from Crypto.Cipher import AES from Crypto.Util.Padding import pad # 輸出的加密檔案名稱 outputFile = 'encrypted.bin' # 要加密的資料(必須為 bytes) data = b'My secret data.' # 以金鑰搭配 CBC 模式建立 cipher 物件 cipher = AES.new(key, AES.MODE_CBC) # 將輸入資料加上 padding 後進行加密 cipheredData = cipher.encrypt(pad(data, AES.block_size)) # 將初始向量與密文寫入檔案 with open(outputFile, "wb") as f: f.write(cipher.iv) f.write(cipheredData)
以下則是 CBC 模式的解密方式:
from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # 輸入的加密檔案名稱 inputFile = 'encrypted.bin' # 從檔案讀取初始向量與密文 with open(inputFile, "rb") as f: iv = f.read(16) # 讀取 16 位元組的初始向量 cipheredData = f.read() # 讀取其餘的密文 # 以金鑰搭配 CBC 模式與初始向量建立 cipher 物件 cipher = AES.new(key, AES.MODE_CBC, iv=iv) # 解密後進行 unpadding originalData = unpad(cipher.decrypt(cipheredData), AES.block_size) # 輸出解密後的資料 print(originalData)
b'My secret data.'
AES 的 CFB 模式跟 CBC 模式很類似,不過資料在加密之前不需要經過 padding 處理。
from Crypto.Cipher import AES # 輸出的加密檔案名稱 outputFile = 'encrypted.bin' # 要加密的資料(必須為 bytes) data = b'My secret data.' # 以金鑰搭配 CFB 模式建立 cipher 物件 cipher = AES.new(key, AES.MODE_CFB) # 將輸入資料進行加密 cipheredData = cipher.encrypt(data) # 將初始向量與密文寫入檔案 with open(outputFile, "wb") as f: f.write(cipher.iv) f.write(cipheredData)
以下則是 CFB 模式的解密方式:
from Crypto.Cipher import AES # 輸入的加密檔案名稱 inputFile = 'encrypted.bin' # 從檔案讀取初始向量與密文 with open(inputFile, "rb") as f: iv = f.read(16) # 讀取 16 位元組的初始向量 cipheredData = f.read() # 讀取其餘的密文 # 以金鑰搭配 CFB 模式與初始向量建立 cipher 物件 cipher = AES.new(key, AES.MODE_CFB, iv=iv) # 解密資料 originalData = cipher.decrypt(cipheredData) # 輸出解密後的資料 print(originalData)
b'My secret data.'
AES 的 EAX 加密模式會產生 nonce 與 tag,這兩項必須連同密文一起儲存起來。
from Crypto.Cipher import AES # 輸出的加密檔案名稱 outputFile = 'encrypted.bin' # 要加密的資料(必須為 bytes) data = b'My secret data.' # 以金鑰搭配 EAX 模式建立 cipher 物件 cipher = AES.new(key, AES.MODE_EAX) # 將輸入資料進行加密 cipheredData, tag = cipher.encrypt_and_digest(data) # 將 nonce、tag 與密文寫入檔案 with open(outputFile, "wb") as f: f.write(cipher.nonce) f.write(tag) f.write(cipheredData)
以下則是 EAX 模式的解密方式:
from Crypto.Cipher import AES # 輸入的加密檔案名稱 inputFile = 'encrypted.bin' # 從檔案讀取初始向量與密文 with open(inputFile, "rb") as f: nonce = f.read(16) # 讀取 16 位元組的 nonce tag = f.read(16) # 讀取 16 位元組的 tag cipheredData = f.read() # 讀取其餘的密文 # 以金鑰搭配 EAX 模式與 nonce 建立 cipher 物件 cipher = AES.new(key, AES.MODE_EAX, nonce) # 解密並驗證資料 originalData = cipher.decrypt_and_verify(cipheredData, tag) # 輸出解密後的資料 print(originalData)
b'My secret data.'
如果要將密文等資料儲存至資料庫或是進行網路傳輸,可以考慮將資料經過 base64 編碼之後,放在一個 JSON 檔案中,以下是一個簡單的範例。
import json from base64 import b64encode, b64decode # 要儲存的原始資料 ciphertext = b'...' iv = b'...' # 建立字典結構 outputJSON = { 'ciphertext': b64encode(ciphertext).decode('utf-8'), 'iv': b64encode(iv).decode('utf-8') } # 儲存為 JSON 檔案 with open('encrypted.json', 'w') as f: json.dump(outputJSON, f) # 讀取 JSON 檔案 with open('encrypted.json') as f: inputJSON = json.load(f) # 取用資料 ciphertext = b64decode(inputJSON['ciphertext'].encode('utf-8')) iv = b64decode(inputJSON['iv'].encode('utf-8'))