前言

  • 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

相关阅读

本文出自 qbit snap

qbit
268 声望279 粉丝