Python AES 加密和解密(qbit)

qbit發表於2024-10-28

前言

  • 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
  • PKCS7 填充與反填充
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! 你好

image.png

相關閱讀

  • AES 線上加解密:https://config.net.cn/tools/AES.html
本文出自 qbit snap

相關文章