Python pycryptodome類庫使用學習總結

授客發表於2024-09-20

AES資料加解密

以下程式碼生成一個新的AES-128金鑰,並將一段資料加密到一個檔案中。我們使用 CTR 模式(這是一種 經典操作模式, 簡單但不再推薦)。
僅使用CTR,接收者無法檢測到密文(即加密資料)在傳輸過程中是否被修改。為了應對這種風險,例中還附加了一個MAC身份驗證標籤(採用SHA256模式的HMAC),該標籤由第二個金鑰製成。

# -*- coding:utf-8 -*-


from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
from Crypto.Random import get_random_bytes

data = 'secret data to transmit'.encode()

aes_key = get_random_bytes(16) # 返回一個包含適合加密使用的隨機位元組的位元組物件
print('aes_key: ', aes_key) # aes_key:  b'\xf6Ke|/M\x93tv\n\xec\x1ej\xb4k\xab'

cipher = AES.new(aes_key, AES.MODE_CTR) # 建立一個 AES cipher
ciphertext = cipher.encrypt(data) # 加密資料,返回位元組物件

hmac_key = get_random_bytes(16)
print('hmac_key: ', hmac_key) # hmac_key:  b'hU\xe6O\x8a\xc5\xa4g\xf3"\xc1K,f\x96\xdb'

hmac = HMAC.new(hmac_key, digestmod=SHA256)
tag = hmac.update(cipher.nonce + ciphertext).digest()

with open("encrypted.bin", "wb") as f:
    f.write(tag)
    f.write(cipher.nonce)
    f.write(ciphertext)

# 與接收者安全共享 aes_key和hmc_key
# encrypted.bin 可透過非安全通道傳輸

最後,接收者可以安全地載入回資料(如果他們知道這兩個金鑰(aes_keyhmc_key的話))。請注意,當檢測到篡改時,程式碼會丟擲ValueError異常。

import sys
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256

aes_key = b'\xf6Ke|/M\x93tv\n\xec\x1ej\xb4k\xab'
hmac_key = b'hU\xe6O\x8a\xc5\xa4g\xf3"\xc1K,f\x96\xdb'

with open("encrypted.bin", "rb") as f:
    tag = f.read(32)
    nonce = f.read(8)
    ciphertext = f.read()

try:
    hmac = HMAC.new(hmac_key, digestmod=SHA256)
    tag = hmac.update(nonce + ciphertext).verify(tag)
except ValueError:
    print("The message was modified!")
    sys.exit(1)

cipher = AES.new(aes_key, AES.MODE_CTR, nonce=nonce)
message = cipher.decrypt(ciphertext) # 解密資料,返回位元組物件
print("Message:", message.decode())  # 輸出:Message: secret data to transmit

一步完成資料加密和驗證

上一節中的程式碼包含三個微妙但重要的設計決策:ciphernonce經過驗證,驗證在加密後執行,且加密和驗證使用兩個不相關的金鑰。安全地組合加密原語並不容易,因此已經建立了更現代的加密cipher模式,OCB mode (查閱其它 經過身份驗證的加密模式EAX, GCM, CCM, SIV)。

# -*- coding:utf-8 -*-

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

data = 'secret data to transmit'.encode()

aes_key = get_random_bytes(16)
print('aes_key: ', aes_key) # aes_key:  b'p.\xb7iD\x8a\xb6\xdc\x1e\x80E\xf4k:\xb4q'

cipher = AES.new(aes_key, AES.MODE_OCB)
ciphertext, tag = cipher.encrypt_and_digest(data)
assert len(cipher.nonce) == 15

with open("encrypted.bin", "wb") as f:
    f.write(tag)
    f.write(cipher.nonce)
    f.write(ciphertext)

# 與接收者安全共享 aes_key
# encrypted.bin 可透過非安全通道傳輸

資料解密

# -*- coding:utf-8 -*-


from Crypto.Cipher import AES

aes_key = b'p.\xb7iD\x8a\xb6\xdc\x1e\x80E\xf4k:\xb4q'

with open("encrypted.bin", "rb") as f:
    tag = f.read(16)
    nonce = f.read(15)
    ciphertext = f.read()

cipher = AES.new(aes_key, AES.MODE_OCB, nonce=nonce)
try:
    message = cipher.decrypt_and_verify(ciphertext, tag)
except ValueError:
    print("The message was modified!")
    exit(1)

print("Message:", message.decode())

RSA資料加解密

生成RSA秘鑰

以下程式碼生成一個新的RSA金鑰對(secret),並將其儲存到一個受密碼保護的檔案中。使用 指令碼 金鑰推導函式,以阻止字典攻擊。最後,程式碼以ASCII/PEM格式列印RSA公鑰:

from Crypto.PublicKey import RSA

secret_code = "Unguessable"
key = RSA.generate(2048)
encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,
                              protection="scryptAndAES128-CBC",
                              prot_params={'iteration_count':131072})

with open("rsa_key.bin", "wb") as f:
    f.write(encrypted_key)

print(key.publickey().export_key())

以下程式碼讀取私鑰RSA,然後再次列印公鑰

from Crypto.PublicKey import RSA

secret_code = "Unguessable"
encoded_key = open("rsa_key.bin", "rb").read()
key = RSA.import_key(encoded_key, passphrase=secret_code)

print(key.publickey().export_key())

生成公鑰和私鑰

以下程式碼生成儲存在receiver.pem中的公鑰和儲存在private.pem中的私鑰。這些檔案將在下面的示例中使用。每次生成不同的公鑰和私鑰對。

from Crypto.PublicKey import RSA

key = RSA.generate(2048)
private_key = key.export_key()
with open("private.pem", "wb") as f:
    f.write(private_key)

public_key = key.publickey().export_key()
with open("receiver.pem", "wb") as f:
    f.write(public_key)

使用RSA加解密資料

以下程式碼為我們擁有RSA公鑰的接收者加密了一段資料。RSA公鑰儲存在一個名為receiver.pem的檔案中。

為了能夠加密任意數量的資料,使用混合加密方案。為AES會話金鑰的非對稱加密,使用RSA及PKCS1OAEP 。然後,會話金鑰可用於加密所有實際資料。

與第一個示例一樣,使用EAX模式來檢測未經授權的修改

from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP

data = "I met aliens in UFO. Here is the map.".encode("utf-8")

recipient_key = RSA.import_key(open("receiver.pem").read())
session_key = get_random_bytes(16)

# 使用公鑰加密會話key
cipher_rsa = PKCS1_OAEP.new(recipient_key)
enc_session_key = cipher_rsa.encrypt(session_key)

# 使用AES會話key加密資料
cipher_aes = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)

with open("encrypted_data.bin", "wb") as f:
    f.write(enc_session_key)
    f.write(cipher_aes.nonce)
    f.write(tag)
    f.write(ciphertext)

接收方擁有RSA私鑰。將首先使用它解密會話金鑰,然後解密檔案的其餘部分::

from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP

private_key = RSA.import_key(open("private.pem").read())

with open("encrypted_data.bin", "rb") as f:
    enc_session_key = f.read(private_key.size_in_bytes())
    nonce = f.read(16)
    tag = f.read(16)
    ciphertext = f.read()

# 使用私鑰解密會話key
cipher_rsa = PKCS1_OAEP.new(private_key)
session_key = cipher_rsa.decrypt(enc_session_key)

# 使用AES會話key解密資料
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
data = cipher_aes.decrypt_and_verify(ciphertext, tag)
print(data.decode("utf-8"))

參考連線

https://www.pycryptodome.org/src/examples