前言
AES
有多種加密模式,本文選取了最常用的 CBC
模式
Cipher Block Chaining
密碼塊鏈模式
Python 3.11.8
cryptography 43.0.3
loguru 0.7.2
示例程式碼
# encoding: utf-8
# author: qbit
# date: 2024-10-28
# summary: 測試 AES 的加密和解密
import os
import random
import string
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from loguru import logger
def pad(data):
r"""填充函式, 確保資料塊符合AES塊大小要求"""
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(data) + padder.finalize()
return padded_data
def unpad(padded_data):
r"""解填充函式"""
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
data = unpadder.update(padded_data) + unpadder.finalize()
return data
def gen_iv(length: int, mode: int = 0) -> bytes:
r"""Generate Initialization Vector, 生成初始化向量
length: 返回的 bytes 長度, 可選值 16/24/32
mode: 0 使用 os.urandom 生成
1 使用 random.choice 隨機選擇字元生成,字元包括 數字/小寫字母/大寫字母
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
"""
match mode:
case 0:
return os.urandom(16)
case 1:
cand = f"{string.digits}{string.ascii_letters}" # candidate 候選字元
return "".join(random.choice(cand) for _ in range(length)).encode("ascii")
case _:
errMsg = f"Error mode: {mode}"
raise Exception(errMsg)
def encrypt_cbc(plaintext, key):
r"""加密函式, 以 CBC 模式加密"""
# 確保key是16, 24或32位元組長
key = key.encode("utf-8")
# 生成隨機的初始化向量 IV
iv = gen_iv(16, 1)
logger.debug(f"iv: {iv}")
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
padded_plaintext = pad(plaintext.encode("utf-8"))
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
# 返回IV和密文,以便解密時使用
return iv + ciphertext
def decrypt_cbc(ciphertext, key):
r"""解密函式, 以 CBC 模式解密"""
key = key.encode("utf-8")
# 提取IV和密文
iv = ciphertext[:16]
ciphertext = ciphertext[16:]
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
plaintext = unpad(padded_plaintext)
return plaintext.decode("utf-8")
if __name__ == "__main__":
plaintext = "hello qbit! 你好"
logger.debug(f"明文: {plaintext}")
key = "qbit_aestest_ibq" # 金鑰長度必須是16, 24或32位元組
logger.debug(f"key: {key}")
# 加密
ciphertext = encrypt_cbc(plaintext, key)
logger.debug(f"密文: {ciphertext.hex()}")
# 解密
decrypted_plaintext = decrypt_cbc(ciphertext, key)
logger.debug(f"解密後的明文: {decrypted_plaintext}")
- 為了便於線上驗證,示例中生成初始化向量採用了隨機選取
數字/小寫字母/大寫字母
的方式 - 輸出結果
明文: hello qbit! 你好
key: qbit_aestest_ibq
iv: b'SPA09YWy8srAth5D'
密文: 53504130395957793873724174683544 9fdb9559c2724c89d60e606cd69d8635e5a2b7f2585767c2894f4bf04b95fa7c
解密後的明文: hello qbit! 你好
相關閱讀
- AES 線上加解密:https://config.net.cn/tools/AES.html
本文出自 qbit snap