AES-128加密算法的经典实现
Release Date: 2025-05-17
Table of Contents
使用 Python 对 AES-128 密码算法加解密流程进行经典实现,所谓经典就是使用普通计算机和编程语言将算法流程实现。算法采用 ECB 模式,即将明文/密文分为16个字节一组(不足的0填充),每组分别独立进行AES加解密,最后拼接在一起。
原理
AES-128 算法共 11 轮,输入输出均为 128 bit,通过
- 字节代换(SubBytes)
- 行移位(ShiftRows)
- 列混淆(MixColumns)
- 轮密钥加(AddRoundKey)
共 4 个操作完成每轮的运算流程。
整体流程如下图所示:
其中字节代换操作为:
行移位操作为:
列混淆操作为:
轮密钥加操作为:
此外,初始密钥也需要通过密钥扩展流程,生成 11 轮的轮密钥,密钥扩展具体流程如下图所示:
了解以上 AES-128 算法具体的流程,下面就可以开始使用 Python 编写经典实现代码。
实现
首先需要定义一些常量,如 RC 轮常数、S 盒、逆 S 盒、MC 列混淆常熟矩阵以及逆 MC 矩阵。
# 轮常数
RC = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]
# S盒,改为16x16的二维数组形式
S = [
[0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76],
[0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0],
[0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15],
[0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75],
[0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84],
[0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf],
[0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8],
[0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2],
[0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73],
[0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb],
[0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79],
[0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08],
[0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a],
[0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e],
[0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf],
[0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]
]
# 逆S盒,改为16x16的二维数组形式
INV_S = [
[0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb],
[0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb],
[0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e],
[0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25],
[0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92],
[0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84],
[0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06],
[0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b],
[0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73],
[0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e],
[0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b],
[0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4],
[0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f],
[0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef],
[0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61],
[0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]
]
# 列混淆常数矩阵
MC = [
[0x02, 0x03, 0x01, 0x01],
[0x01, 0x02, 0x03, 0x01],
[0x01, 0x01, 0x02, 0x03],
[0x03, 0x01, 0x01, 0x02]
]
INV_MC = [
[0x0e, 0x0b, 0x0d, 0x09],
[0x09, 0x0e, 0x0b, 0x0d],
[0x0d, 0x09, 0x0e, 0x0b],
[0x0b, 0x0d, 0x09, 0x0e]
]
接着是编写一些工具方法,比如将明文按 128 bit 分组,将 128 bit 数据转成 4 x 4 矩阵等等,方便后续加密流程操作。
"""
明文按照16字节分组, 不足16字节用0填充
密钥长度必须为16字节, 不足16字节用0填充, 超过16字节截断
"""
# 将明文按照16字节分组
def plaintext_to_blocks(plaintext):
blocks = []
for i in range(0, len(plaintext), 16):
block = plaintext[i:i + 16]
if len(block) < 16:
block += b'\x00' * (16 - len(block))
blocks.append(block)
return blocks
# 确保密钥长度为16字节
def key_to_16bytes(key):
if len(key) < 16:
key += b'\x00' * (16 - len(key))
return key[:16]
"""
无论明文还是密钥都需要将16字节数据转成4*4的矩阵
16字节数据: D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 D14 D15 D16
4*4矩阵数据: D1 D5 D9 D13
D2 D6 D10 D14
D3 D7 D11 D15
D4 D8 D12 D16
"""
# 将16字节数据转成4*4的矩阵
def bytes_to_matrix(bytes):
matrix = []
for i in range(4):
matrix.append([])
for j in range(4):
matrix[i].append(bytes[i + 4 * j])
return matrix
# 将4*4的矩阵转成16字节数据
def matrix_to_bytes(matrix):
bytes = []
for i in range(4):
for j in range(4):
bytes.append(matrix[j][i])
return bytes
# 将4*4矩阵转成1*4的字列表
def matrix_to_words(matrix):
words = []
for i in range(4):
words.append(matrix[0][i] << 24 | matrix[1][i] << 16 | matrix[2][i] << 8 | matrix[3][i])
return words
# 将1*4的字列表转成4*4矩阵
def words_to_matrix(words):
matrix = []
for i in range(4):
matrix.append([])
for j in range(4):
matrix[i].append((words[j] >> (24 - 8 * i)) & 0xff)
return matrix
# 将一个字转成4个字节列表
def word_to_bytes(word):
bytes = []
for i in range(4):
bytes.append((word >> (24 - 8 * i)) & 0xff)
return bytes
# 将4个字节列表转成一个字
def bytes_to_word(bytes):
word = 0
for i in range(4):
word |= bytes[i] << (24 - 8 * i)
return word
做好以上这些准备操作,下面开始编写算法流程,第一步先编写密钥扩展流程。
"""
1. 密钥扩展: 将初始16字节密钥扩展成R0-R10共11个16字节的轮密钥
将初始密钥转成4*4的矩阵, 按列排列得到4个4字节的字 w0 w1 w2 w3
将w3的每个字节左移一位, b0 b1 b2 b3 -> b1 b2 b3 b0
将w3的每个字节高4位作为行索引, 低4位作为列索引, 进行S盒替换得到 b1' b2' b3' b0'
将b1' b2' b3' b0' 与轮常量RC[j/4] 0 0 0进行异或得到 w3'
w4 = w0 ^ w3', w5 = w1 ^ w4, w6 = w2 ^ w5, w7 = w3 ^ w6, ...
不断循环重复上述过程, 直到得到11个16字节的轮密钥, w44 w45 w46 w47
"""
def key_expansion(key):
# 确保密钥长度为16字节
key = key_to_16bytes(key)
# 将密钥转成4*4的矩阵
key_matrix = bytes_to_matrix(key)
# 按列排列得到4个4字节的字 w0 w1 w2 w3
old_words = matrix_to_words(key_matrix)
# R0-R10共11个16字节的轮密钥
round_keys = []
round_keys.append(key_matrix)
for i in range(10):
words = [0, 0, 0, 0]
old_bytes = word_to_bytes(old_words[3])
# 将w3每个字节左移一位, b0 b1 b2 b3 -> b1 b2 b3 b0
old_bytes = [old_bytes[1], old_bytes[2], old_bytes[3], old_bytes[0]]
# 字节代换
bytes = sub_bytes_bytes(old_bytes)
# 将b1' b2' b3' b0' 与轮常量RC[j/4] 0 0 0进行异或得到 w3'
bytes[0] = bytes[0] ^ RC[i + 1]
# w4 = w0 ^ w0', w5 = w1 ^ w4, w6 = w2 ^ w5, w7 = w3 ^ w6,...
words[3] = bytes_to_word(bytes)
words[0] = old_words[0] ^ words[3]
words[1] = old_words[1] ^ words[0]
words[2] = old_words[2] ^ words[1]
words[3] = old_words[3] ^ words[2]
# 将w4 w5 w6 w7 转成4*4的矩阵, 得到轮密钥
round_keys.append(words_to_matrix(words))
# 将w4 w5 w6 w7 作为下一轮的w0 w1 w2 w3
old_words = words
return round_keys
第二步是编写字节代换流程。
"""
2. 字节代换: 将4*4的矩阵中的每个字节进行S盒替换
"""
# 4*4的矩阵中的每个字节进行S盒替换
def sub_bytes(matrix):
for i in range(4):
for j in range(4):
# 将每个字节中的高4位作为行索引, 低4位作为列索引, 进行S盒替换
matrix[i][j] = S[matrix[i][j] >> 4][matrix[i][j] & 0x0f]
return matrix
# 密钥扩展中 将 1*4 bytes列表中的每个字节进行S盒替换
def sub_bytes_bytes(bytes):
for i in range(4):
bytes[i] = S[bytes[i] >> 4][bytes[i] & 0x0f]
return bytes
# 逆S盒替换
def inv_sub_bytes(matrix):
for i in range(4):
for j in range(4):
# 将每个字节中的高4位作为行索引, 低4位作为列索引, 进行逆S盒替换
matrix[i][j] = INV_S[matrix[i][j] >> 4][matrix[i][j] & 0x0f]
return matrix
接着编写行移位流程。
"""
3. 行移位: 将4*4的矩阵中的每一行进行循环左移
"""
# 4*4的矩阵中 第一行不变, 第二行左移1位, 第三行左移2位, 第四行左移3位
def shift_rows(matrix):
matrix[1] = [matrix[1][1], matrix[1][2], matrix[1][3], matrix[1][0]]
matrix[2] = [matrix[2][2], matrix[2][3], matrix[2][0], matrix[2][1]]
matrix[3] = [matrix[3][3], matrix[3][0], matrix[3][1], matrix[3][2]]
return matrix
# 逆行移位
def inv_shift_rows(matrix):
matrix[1] = [matrix[1][3], matrix[1][0], matrix[1][1], matrix[1][2]]
matrix[2] = [matrix[2][2], matrix[2][3], matrix[2][0], matrix[2][1]]
matrix[3] = [matrix[3][1], matrix[3][2], matrix[3][3], matrix[3][0]]
return matrix
这些流程都比较简单,根据各个部分的原理图就可以很简单地编写出来,下面应该整个 AES 算法中最复杂的部分之一,列混淆。
列混淆的难点在于其中的乘法运算是在 GF (2^8) 上进行的,这在代码中实现还是有一些难度的,下面提供了两种 GF (2^8) 乘法运算的实现方式。
- 思路 1(
multiply1
):看成移动比特串
左乘 0x01 表示不变
左乘 0x02 表示左移1位
左乘 0x03 表示左移1位, 再异或自己(0x03 = 0x01 + 0x02)
a = 0x02 = 左移1位, b = 0xc9 = 0b11001001
a * b = 0b11001001左移一位, 且因为最高位为1, 还需模2^8 = 0b10010010 ^ 0x11b = 0b10001001 = 0x89
- 思路 2(
multiply2
):逐位处理,模拟乘法
将 b 不断右移取最低位, a 不断左移
a = 0x03 = 0b00000011, b = 0x07 = 0b00000111
a 左移0位 = 0b00000011, 左移1位 = 0b00000110, 左移2位 = 0b00001100
b 右边第1位 = 1, 右边第2位 = 1, 右边第3位 = 1, 其次为0
1 * 0b00000011 ^ 1 * 0b00000110 ^ 1 * 0b00001100 = 0b00001001 = 0x09
如果 a 左移后最高位为1, 则需要模2^8 = 0x11b
模拟乘法:
0b00000011
* 0b00000111
----------
0b00000011
^ 0b00000110
^ 0b00001100
----------
= 0b00001001 = 0x09
整体代码实现:
"""
4. 列混淆: 将4*4的矩阵中的每一列进行列混合运算
4*4的矩阵左乘一个固定的矩阵c(x), 得到新的矩阵
c(x) = 02 03 01 01
01 02 03 01
01 01 02 03
03 01 01 02
乘法在GF(2^8)上进行, 即 f*g mod (x^8 + x^4 + x^3 + x + 1)
"""
# 4*4的矩阵中的每一列进行列混淆运算
def mix_columns(matrix):
new_matrix = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
for i in range(4):
for j in range(4):
new_matrix[i][j] = mix_column(matrix, i, j)
return new_matrix
# 列混淆运算
def mix_column(matrix, i, j):
result = 0
for k in range(4):
result ^= multiply2(MC[i][k], matrix[k][j])
return result
# 逆列混淆
def inv_mix_columns(matrix):
new_matrix = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
for i in range(4):
for j in range(4):
new_matrix[i][j] = inv_mix_column(matrix, i, j)
return new_matrix
# 逆列混淆运算
def inv_mix_column(matrix, i, j):
result = 0
for k in range(4):
result ^= multiply2(INV_MC[i][k], matrix[k][j])
return result
# GF(2^8)上的乘法运算
# 思路1
def multiply1(a, b):
result = 0
if a == 0x01:
return b
elif a == 0x02:
result = b << 1
if b & 0x80:
result ^= 0x11b
return result
elif a == 0x03:
result = b << 1
if b & 0x80:
result ^= 0x11b
return result ^ b
else:
raise ValueError("Invalid multiplication factor")
# 思路2
def multiply2(a, b):
result = 0 # 初始化结果为 0
for i in range(8): # 循环 8 次,因为每个字节有 8 位
if b & 1: # 检查 b 的最低位是否为 1
result ^= a # 如果最低位为 1,则将 a 异或到结果中
a <<= 1 # 将 a 左移一位
if a & 0x100: # 检查 a 左移后是否溢出(即最高位是否为 1)
a ^= 0x11b # 如果溢出,则对 a 进行模运算,即异或 0x11b
b >>= 1 # 将 b 右移一位,准备检查下一位
# 返回最终的乘法结果
return result
轮密钥加操作则十分简单。
"""
5. 轮密钥加: 将4*4的矩阵中的每个字节与轮密钥中的对应字节进行异或
"""
def add_round_key(matrix, round_key):
for i in range(4):
for j in range(4):
matrix[i][j] ^= round_key[i][j]
return matrix
完成了算法各个部分的流程实现,最后就是加密解密和验证操作了。
# 加密操作
def encrypt(plaintext, key):
# 将明文按照16字节分组
blocks = plaintext_to_blocks(plaintext)
# 密钥扩展
round_keys = key_expansion(key)
# 加密
ciphertext = b""
for block in blocks:
# 将16字节数据转成4*4的矩阵
block_matrix = bytes_to_matrix(block)
# R0 轮密钥加
block_matrix = add_round_key(block_matrix, round_keys[0])
# R1-R10 轮, R10 轮不进行列混淆
for i in range(1, 11):
# 字节代换
block_matrix = sub_bytes(block_matrix)
# 行移位
block_matrix = shift_rows(block_matrix)
# 列混淆
if i != 10:
block_matrix = mix_columns(block_matrix)
# 轮密钥加
block_matrix = add_round_key(block_matrix, round_keys[i])
# ECB模式: 将每组密文拼接在一起 转成16进制
ciphertext += bytes(matrix_to_bytes(block_matrix))
return ciphertext
# 解密操作
def decrypt(ciphertext, key):
# 将密文按照16字节分组
blocks = plaintext_to_blocks(ciphertext)
# 密钥扩展
round_keys = key_expansion(key)
# 解密
plaintext = b""
for block in blocks:
# 将16字节数据转成4*4的矩阵
block_matrix = bytes_to_matrix(block)
# R10-R1 轮, R10 轮不进行列混淆
for i in range(10, 0, -1):
# 轮密钥加
block_matrix = add_round_key(block_matrix, round_keys[i])
# 列混淆
if i!= 10:
block_matrix = inv_mix_columns(block_matrix)
# 逆行移位
block_matrix = inv_shift_rows(block_matrix)
# 逆字节代换
block_matrix = inv_sub_bytes(block_matrix)
# R0 轮密钥加
block_matrix = add_round_key(block_matrix, round_keys[0])
# ECB模式: 将每组明文拼接在一起 转成16进制
plaintext += bytes(matrix_to_bytes(block_matrix))
return plaintext
验证代码是否正确。
def encrypt_test():
try:
# test case 1
plaintext = bytes.fromhex("00112233445566778899AABBCCDDEEFF")
key = bytes.fromhex("2B7E151628AED2A6ABF7158809CF4F3C")
ciphertext = encrypt(plaintext, key)
assert ciphertext == bytes.fromhex("8DF4E9AAC5C7573A27D8D055D6E4D64B")
# test case 2
plaintext = bytes.fromhex("34F61A19C754110DA892362BAC078B99")
key = bytes.fromhex("0123456789ABCDEF0123456789ABCDEF")
ciphertext = encrypt(plaintext, key)
assert ciphertext == bytes.fromhex("5CFB2FD936BB4F0E372A8246044183E2")
# test case 3
plaintext = bytes.fromhex("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
key = bytes.fromhex("11111111111111111111111111111111")
ciphertext = encrypt(plaintext, key)
assert ciphertext == bytes.fromhex("24E08A84E6D1C9FD104A2BEB32D783D5")
# test case 4
plaintext = bytes.fromhex("12345678123456781234567812345678")
key = bytes.fromhex("12345678123456781234567812345678")
ciphertext = encrypt(plaintext, key)
assert ciphertext == bytes.fromhex("D7EEEE18C420FAF0DC7DB5CA73A2B817")
# test case 5
plaintext = bytes.fromhex("3243F6A8885A308D313198A2E0370734")
key = bytes.fromhex("2B7E151628AED2A6ABF7158809CF4F3C")
ciphertext = encrypt(plaintext, key)
assert ciphertext == bytes.fromhex("3925841D02DC09FBDC118597196A0B32")
except AssertionError:
print("X encrypt test failed X")
else:
print("√ encrypt test passed √")
def decrypt_test():
try:
# test case 1
ciphertext = bytes.fromhex("8DF4E9AAC5C7573A27D8D055D6E4D64B")
key = bytes.fromhex("2B7E151628AED2A6ABF7158809CF4F3C")
plaintext = decrypt(ciphertext, key)
assert plaintext == bytes.fromhex("00112233445566778899AABBCCDDEEFF")
# test case 2
ciphertext = bytes.fromhex("5CFB2FD936BB4F0E372A8246044183E2")
key = bytes.fromhex("0123456789ABCDEF0123456789ABCDEF")
plaintext = decrypt(ciphertext, key)
assert plaintext == bytes.fromhex("34F61A19C754110DA892362BAC078B99")
# test case 3
ciphertext = bytes.fromhex("24E08A84E6D1C9FD104A2BEB32D783D5")
key = bytes.fromhex("11111111111111111111111111111111")
plaintext = decrypt(ciphertext, key)
assert plaintext == bytes.fromhex("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
# test case 4
ciphertext = bytes.fromhex("D7EEEE18C420FAF0DC7DB5CA73A2B817")
key = bytes.fromhex("12345678123456781234567812345678")
plaintext = decrypt(ciphertext, key)
assert plaintext == bytes.fromhex("12345678123456781234567812345678")
# test case 5
ciphertext = bytes.fromhex("3925841D02DC09FBDC118597196A0B32")
key = bytes.fromhex("2B7E151628AED2A6ABF7158809CF4F3C")
plaintext = decrypt(ciphertext, key)
assert plaintext == bytes.fromhex("3243F6A8885A308D313198A2E0370734")
except AssertionError:
print("X decrypt test failed X")
else:
print("√ decrypt test passed √")