1. 项目概述:为什么我们要从底层实现AES?

如果你正在学习密码学,或者想深入理解现代加密技术的内核,那么“从底层实现AES”这个项目绝对是一个绝佳的切入点。AES(高级加密标准)早已无处不在,从你手机里的加密通讯,到网上银行的交易保护,再到你电脑上文件的加密存储,背后都有它的身影。很多朋友学习加密,可能只是调用一个库函数,比如Python里的 cryptography 库,一行 cipher.encrypt() 就搞定了。这当然高效、安全,但对于理解“加密究竟是如何发生的”这个问题,却隔着一层厚厚的黑箱。

这个项目的核心价值,就在于亲手拆开这个黑箱。我们不依赖任何现成的加密库(除了最基础的数据类型转换),仅使用Python标准库,从最原始的字节操作开始,一步步构建出完整的AES-128加解密流程。你会亲手实现字节代换、行移位、列混合、轮密钥加这些核心操作,看着一段明文如何经过多轮“搅拌”变成面目全非的密文,再如何被神奇地还原回来。这个过程不仅能让你彻底吃透AES的算法原理,更能极大提升你对位运算、矩阵操作和算法设计的底层编码能力。无论是为了通过“头歌”这类实践平台的考核,还是为了夯实自己的计算机科学基础,亦或是满足对密码学纯粹的好奇心,这个项目都值得你投入时间。

2. AES-128算法核心原理拆解

在动手写代码之前,我们必须先理解AES-128到底在干什么。AES是一种对称分组密码算法,所谓“对称”是指加密和解密使用同一把密钥;“分组”是指它每次处理固定长度的一块数据,对于AES-128,这个长度是128位,也就是16个字节。

2.1 状态矩阵与轮结构

AES的所有操作都是在一个4x4的字节矩阵(称为“状态State”)上进行的。加密开始时,16字节的明文会被按列优先的顺序填充到这个矩阵中。例如,明文字节 P0, P1, ..., P15 会按如下方式排列:

[ P0  P4  P8  P12 ]
[ P1  P5  P9  P13 ]
[ P2  P6  P10 P14 ]
[ P3  P7  P11 P15 ]

AES-128加密过程包含10轮(Round)计算。每一轮(除最后一轮稍有不同)都包含四个基本步骤:字节代换(SubBytes)、行移位(ShiftRows)、列混合(MixColumns)、轮密钥加(AddRoundKey)。第一轮开始前,会先进行一次轮密钥加(称为初始轮),最后一轮则省略列混合步骤。

2.2 核心操作原理解析

字节代换(SubBytes) : 这是AES中唯一的非线性变换,是算法安全性的重要来源。它通过一个被称为S盒(Substitution-box)的查找表,将状态中的每一个字节替换为另一个字节。这个S盒是经过精心设计的,具有良好的非线性特性,能有效抵抗密码分析。在底层实现中,S盒是一个长度为256的字节数组,输入字节作为索引,输出对应的替换值。

行移位(ShiftRows) : 这是一个线性扩散操作。状态矩阵的第0行保持不变;第1行循环左移1个字节;第2行循环左移2个字节;第3行循环左移3个字节。这个操作的目的是让同一列中的字节分散到不同的列中,在后续的列混合中实现更充分的扩散。

列混合(MixColumns) : 这是AES中最复杂的变换,也在状态矩阵的每一列上独立进行。它将每一列的4个字节看作有限域GF(2^8)上的一个多项式,并与一个固定的多项式 c(x) = {03}x^3 + {01}x^2 + {01}x + {02} 进行模 x^4+1 乘法。在实际计算中,这等价于对每一列进行一个固定的矩阵乘法。这个操作极大地增强了字节之间的关联性,提供了极强的扩散效果。

轮密钥加(AddRoundKey) : 这是最简单的一步,将当前的状态矩阵与当前轮的轮密钥(也是一个4x4矩阵)进行逐字节的异或(XOR)操作。密钥信息通过这一步注入到加密过程中。

密钥扩展(Key Expansion) : AES-128的原始密钥是16字节。加密需要11个轮密钥(初始轮+10个计算轮),每个轮密钥16字节。密钥扩展算法就是从一个种子密钥生成这11个轮密钥的过程。它涉及字节的循环移位、S盒代换以及与轮常量(Rcon)的异或操作,确保了轮密钥之间的相关性很低。

注意:列混合和密钥扩展中涉及到的有限域GF(2^8)上的乘法,是AES底层实现的难点和关键。它不同于普通的整数乘法,需要先进行多项式乘法,然后模一个不可约多项式 m(x) = x^8 + x^4 + x^3 + x + 1 (对应十六进制0x11b)。在代码实现时,我们通常通过预先计算好的查找表或条件判断来实现。

3. 核心模块的Python实现

理解了原理,我们就可以开始用Python搭建我们的AES“发动机”了。我们将分模块构建,每个函数只负责一个明确的原子操作。

3.1 基础工具函数与常量定义

任何工程都需要好用的工具。我们首先实现一些基础函数和定义核心常量。

def bytes_to_matrix(text):
    """将16字节数据转换为4x4状态矩阵(列优先)。"""
    return [list(text[i:i+4]) for i in range(0, 16, 4)]

def matrix_to_bytes(matrix):
    """将4x4状态矩阵转换回16字节数据(列优先)。"""
    return bytes(sum(zip(*matrix), ())) # 巧妙利用zip转置和sum拼接

def xor_bytes(a, b):
    """对两个等长的字节序列进行逐字节异或。"""
    return bytes(i^j for i, j in zip(a, b))

# AES S盒(Substitution Box)
s_box = (
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    # ... 此处省略中间内容,实际代码需包含完整的256个值
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
)

# AES 逆S盒(用于解密)
inv_s_box = (
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    # ... 此处省略中间内容,实际代码需包含完整的256个值
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
)

# 轮常量(Round Constant) Rcon,用于密钥扩展
rcon = (0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)
# 注意:rcon[0]占位不用,rcon[i]对应第i轮

这里有个 实操心得 matrix_to_bytes 函数中 sum(zip(*matrix), ()) 的写法比较精妙。 zip(*matrix) 实现了矩阵的转置(将列优先变回行优先), sum(..., ()) 则将元组序列扁平化。你也可以用更直观的双层循环,但这种方式更体现Python的风格。

3.2 密钥扩展的实现

密钥扩展是驱动整个加密过程的燃料生成器。它的输入是16字节的原始密钥,输出是44个字(每个字4字节,共176字节)的扩展密钥数组,每16字节作为一个轮密钥。

def sub_word(word):
    """对一个4字节字进行S盒代换。"""
    return bytes(s_box[b] for b in word)

def rot_word(word):
    """将一个4字节字循环左移一个字节。"""
    return word[1:] + word[0:1]

def key_expansion(key):
    """
    扩展16字节的密钥为44个字(176字节)的扩展密钥。
    返回一个列表,每4个字(16字节)可以作为一个轮密钥。
    """
    key = bytes(key) # 确保输入是字节序列
    w = [0]*44 # 初始化44个字的空间

    # 1. 填充前4个字为原始密钥
    for i in range(4):
        w[i] = key[4*i : 4*i+4]

    # 2. 递归生成后续的字
    for i in range(4, 44):
        temp = w[i-1]
        if i % 4 == 0:
            # 每4个字,需要进行变换:RotWord -> SubWord -> XOR Rcon
            temp = xor_bytes(sub_word(rot_word(temp)), bytes([rcon[i//4], 0, 0, 0]))
        # 当前字 = 前4个字 XOR 临时字
        w[i] = xor_bytes(w[i-4], temp)

    # 3. 将字列表转换为轮密钥列表(每个轮密钥16字节)
    round_keys = []
    for i in range(0, 44, 4):
        round_key = b''.join(w[i:i+4])
        round_keys.append(round_key)
    return round_keys

注意:密钥扩展是单向的,且与加密过程独立。这意味着我们可以在加密开始前一次性生成所有轮密钥并保存,加解密时直接按轮数取用,避免每轮重复计算,这是常见的性能优化点。

3.3 加密核心步骤的实现

现在,我们来实现加密的四个核心步骤。我们约定,所有函数都直接操作4x4的字节矩阵(列表的列表)。

字节代换 :最简单,就是查表。

def sub_bytes(state):
    """对状态矩阵进行字节代换。"""
    for i in range(4):
        for j in range(4):
            state[i][j] = s_box[state[i][j]]
    return state

行移位 :操作行索引。

def shift_rows(state):
    """对状态矩阵进行行移位。"""
    # 第0行不变
    # 第1行左移1位
    state[1][0], state[1][1], state[1][2], state[1][3] = state[1][1], state[1][2], state[1][3], state[1][0]
    # 第2行左移2位
    state[2][0], state[2][1], state[2][2], state[2][3] = state[2][2], state[2][3], state[2][0], state[2][1]
    # 第3行左移3位(相当于右移1位)
    state[3][0], state[3][1], state[3][2], state[3][3] = state[3][3], state[3][0], state[3][1], state[3][2]
    return state

列混合 :这是最复杂的一步,需要实现有限域GF(2^8)上的乘法。我们首先实现一个辅助函数 gf_multiply

def gf_multiply(a, b):
    """在GF(2^8)上乘法,模不可约多项式 x^8 + x^4 + x^3 + x + 1 (0x11b)。"""
    p = 0
    for _ in range(8):
        if b & 1: # 如果b的最低位是1
            p ^= a   # 则将a加到p上(异或)
        high_bit_set = a & 0x80 # 判断a的最高位是否为1
        a <<= 1 # a左移一位
        if high_bit_set:
            a ^= 0x11b # 如果溢出,则模0x11b
        b >>= 1 # b右移一位
    return p

def mix_columns(state):
    """对状态矩阵进行列混合。"""
    new_state = [[0]*4 for _ in range(4)]
    # 固定矩阵:[[2,3,1,1], [1,2,3,1], [1,1,2,3], [3,1,1,2]]
    # 对每一列进行计算
    for c in range(4):
        new_state[0][c] = gf_multiply(0x02, state[0][c]) ^ gf_multiply(0x03, state[1][c]) ^ state[2][c] ^ state[3][c]
        new_state[1][c] = state[0][c] ^ gf_multiply(0x02, state[1][c]) ^ gf_multiply(0x03, state[2][c]) ^ state[3][c]
        new_state[2][c] = state[0][c] ^ state[1][c] ^ gf_multiply(0x02, state[2][c]) ^ gf_multiply(0x03, state[3][c])
        new_state[3][c] = gf_multiply(0x03, state[0][c]) ^ state[1][c] ^ state[2][c] ^ gf_multiply(0x02, state[3][c])
    return new_state

轮密钥加 :将状态矩阵与轮密钥矩阵逐字节异或。

def add_round_key(state, round_key):
    """轮密钥加。state和round_key都是4x4字节矩阵。"""
    for i in range(4):
        for j in range(4):
            state[i][j] ^= round_key[i][j] # 注意:round_key矩阵也是列优先存储的
    return state

这里有一个 关键细节 add_round_key 函数中的 round_key 参数,我们传入的是已经转换为4x4矩阵的轮密钥。在加密主函数中,我们需要先将字节序列的轮密钥转换为矩阵,再传入。 gf_multiply 函数的实现使用了经典的“移位加”算法,这是实现有限域乘法的标准方法之一,务必理解其每一步的含义。

3.4 解密核心步骤的实现

解密是加密的逆过程,每一步都有对应的逆操作。逆字节代换(InvSubBytes)和逆行移位(InvShiftRows)比较简单,分别是查逆S盒和进行反向的循环移位。

def inv_sub_bytes(state):
    """对状态矩阵进行逆字节代换。"""
    for i in range(4):
        for j in range(4):
            state[i][j] = inv_s_box[state[i][j]]
    return state

def inv_shift_rows(state):
    """对状态矩阵进行逆行移位。"""
    # 第0行不变
    # 第1行右移1位(即左移3位)
    state[1][0], state[1][1], state[1][2], state[1][3] = state[1][3], state[1][0], state[1][1], state[1][2]
    # 第2行右移2位(即左移2位)
    state[2][0], state[2][1], state[2][2], state[2][3] = state[2][2], state[2][3], state[2][0], state[2][1]
    # 第3行右移3位(即左移1位)
    state[3][0], state[3][1], state[3][2], state[3][3] = state[3][1], state[3][2], state[3][3], state[3][0]
    return state

逆列混合(InvMixColumns) :这是解密中最复杂的部分。它对应一个固定的逆矩阵乘法。逆矩阵是:

[0x0e, 0x0b, 0x0d, 0x09]
[0x09, 0x0e, 0x0b, 0x0d]
[0x0d, 0x09, 0x0e, 0x0b]
[0x0b, 0x0d, 0x09, 0x0e]

实现方式与 mix_columns 类似,只是系数变了。

def inv_mix_columns(state):
    """对状态矩阵进行逆列混合。"""
    new_state = [[0]*4 for _ in range(4)]
    # 逆矩阵系数
    for c in range(4):
        new_state[0][c] = gf_multiply(0x0e, state[0][c]) ^ gf_multiply(0x0b, state[1][c]) ^ gf_multiply(0x0d, state[2][c]) ^ gf_multiply(0x09, state[3][c])
        new_state[1][c] = gf_multiply(0x09, state[0][c]) ^ gf_multiply(0x0e, state[1][c]) ^ gf_multiply(0x0b, state[2][c]) ^ gf_multiply(0x0d, state[3][c])
        new_state[2][c] = gf_multiply(0x0d, state[0][c]) ^ gf_multiply(0x09, state[1][c]) ^ gf_multiply(0x0e, state[2][c]) ^ gf_multiply(0x0b, state[3][c])
        new_state[3][c] = gf_multiply(0x0b, state[0][c]) ^ gf_multiply(0x0d, state[1][c]) ^ gf_multiply(0x09, state[2][c]) ^ gf_multiply(0x0e, state[3][c])
    return new_state

注意:解密时轮密钥的使用顺序与加密相反。加密是从第0轮密钥用到第10轮密钥,而解密则是从第10轮密钥开始,倒序使用回第0轮密钥。此外,解密轮的顺序是:逆字节代换、逆行移位、逆列混合、轮密钥加(注意列混合在轮密钥加之前)。

4. 整合与主流程实现

各个零件已经准备就绪,现在我们需要把它们组装起来,形成完整的加密和解密流水线。

4.1 加密主函数

加密函数 aes_encrypt_block 负责对一个16字节的明文块进行加密。

def aes_encrypt_block(plaintext, key):
    """
    使用AES-128加密一个16字节的明文块。
    参数:
        plaintext: 16字节的明文字节序列。
        key: 16字节的密钥字节序列。
    返回:
        16字节的密文字节序列。
    """
    # 1. 密钥扩展
    round_keys = key_expansion(key) # 得到11个轮密钥(字节序列)

    # 2. 初始化状态矩阵
    state = bytes_to_matrix(plaintext)

    # 3. 初始轮密钥加(使用第0轮密钥)
    round_key_matrix = bytes_to_matrix(round_keys[0])
    state = add_round_key(state, round_key_matrix)

    # 4. 进行9轮标准轮函数
    for i in range(1, 10):
        state = sub_bytes(state)
        state = shift_rows(state)
        state = mix_columns(state)
        round_key_matrix = bytes_to_matrix(round_keys[i])
        state = add_round_key(state, round_key_matrix)

    # 5. 最后一轮(省略MixColumns)
    state = sub_bytes(state)
    state = shift_rows(state)
    round_key_matrix = bytes_to_matrix(round_keys[10])
    state = add_round_key(state, round_key_matrix)

    # 6. 将状态矩阵转换回字节序列
    ciphertext = matrix_to_bytes(state)
    return ciphertext

4.2 解密主函数

解密函数 aes_decrypt_block 是加密的逆过程。

def aes_decrypt_block(ciphertext, key):
    """
    使用AES-128解密一个16字节的密文块。
    参数:
        ciphertext: 16字节的密文字节序列。
        key: 16字节的密钥字节序列。
    返回:
        16字节的明文字节序列。
    """
    # 1. 密钥扩展(与加密相同)
    round_keys = key_expansion(key)

    # 2. 初始化状态矩阵
    state = bytes_to_matrix(ciphertext)

    # 3. 初始轮(对应加密的最后一轮,顺序相反)
    round_key_matrix = bytes_to_matrix(round_keys[10])
    state = add_round_key(state, round_key_matrix)
    state = inv_shift_rows(state)
    state = inv_sub_bytes(state)

    # 4. 进行9轮标准逆轮函数
    for i in range(9, 0, -1): # i从9递减到1
        round_key_matrix = bytes_to_matrix(round_keys[i])
        state = add_round_key(state, round_key_matrix)
        state = inv_mix_columns(state)
        state = inv_shift_rows(state)
        state = inv_sub_bytes(state)

    # 5. 最终轮密钥加(使用第0轮密钥)
    round_key_matrix = bytes_to_matrix(round_keys[0])
    state = add_round_key(state, round_key_matrix)

    # 6. 将状态矩阵转换回字节序列
    plaintext = matrix_to_bytes(state)
    return plaintext

一个重要的实操心得 :对比加密和解密的代码,你会发现它们并不是简单的镜像对称。解密轮中 add_round_key inv_mix_columns inv_shift_rows inv_sub_bytes 的顺序,与加密轮中 sub_bytes shift_rows mix_columns add_round_key 的顺序并不完全相反。这是因为列混合和轮密钥加这两个线性操作在数学上不满足交换律,但满足一个特定的等价关系: InvMixColumns(AddRoundKey(state, key)) = AddRoundKey(InvMixColumns(state), InvMixColumns(key)) 。标准解密流程(如上所示)是效率最高的一种等价形式。理解这一点,才能算真正吃透了AES的结构。

5. 工作模式与完整数据加解密

我们上面实现的是对单个16字节分组的加解密。但实际数据长度是任意的,这就需要引入 工作模式 。最常用的模式是CBC(密码分组链接)模式,它能有效隐藏明文的模式。

5.1 CBC模式原理与实现

CBC模式需要一个额外的16字节初始化向量(IV)。加密时,第一个明文块先与IV异或,再进行AES加密,得到的密文块又作为下一个明文块的“IV”,如此链接下去。解密则是反向过程。

def pad(data):
    """PKCS#7填充:确保数据长度是16的倍数。"""
    pad_len = 16 - (len(data) % 16)
    return data + bytes([pad_len] * pad_len)

def unpad(data):
    """PKCS#7去填充。"""
    pad_len = data[-1]
    # 简单的填充有效性检查
    if pad_len < 1 or pad_len > 16 or data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("无效的PKCS#7填充")
    return data[:-pad_len]

def aes_encrypt_cbc(plaintext, key, iv):
    """使用AES-128 CBC模式加密任意长度数据。"""
    plaintext = pad(plaintext)
    ciphertext = b''
    prev_block = iv # 前一个密文块,初始为IV
    for i in range(0, len(plaintext), 16):
        block = plaintext[i:i+16]
        # CBC加密核心:明文块与前一个密文块异或后,再进行AES加密
        xored_block = xor_bytes(block, prev_block)
        encrypted_block = aes_encrypt_block(xored_block, key)
        ciphertext += encrypted_block
        prev_block = encrypted_block # 更新前一个密文块
    return ciphertext

def aes_decrypt_cbc(ciphertext, key, iv):
    """使用AES-128 CBC模式解密数据。"""
    if len(ciphertext) % 16 != 0:
        raise ValueError("密文长度必须是16的倍数")
    plaintext = b''
    prev_block = iv
    for i in range(0, len(ciphertext), 16):
        block = ciphertext[i:i+16]
        # CBC解密核心:先AES解密,再与前一个密文块异或
        decrypted_block = aes_decrypt_block(block, key)
        plaintext_block = xor_bytes(decrypted_block, prev_block)
        plaintext += plaintext_block
        prev_block = block # 注意:解密时,前一个密文块是当前未解密的密文块
    return unpad(plaintext)

5.2 完整示例与测试

让我们用一个完整的例子来测试我们的AES实现。

def main():
    # 测试密钥、IV和明文
    key = b'This is a key123' # 16字节密钥
    iv = b'This is an IV456'  # 16字节IV
    plaintext = b'Hello, AES! This is a test message that is longer than one block.'

    print(f"原始明文: {plaintext}")
    print(f"明文长度: {len(plaintext)}")

    # 加密
    ciphertext = aes_encrypt_cbc(plaintext, key, iv)
    print(f"\nCBC加密后密文 (十六进制): {ciphertext.hex()}")

    # 解密
    decrypted = aes_decrypt_cbc(ciphertext, key, iv)
    print(f"\n解密后明文: {decrypted}")
    print(f"解密是否成功: {decrypted == plaintext}")

    # 额外测试:单个分组加密/解密
    print("\n--- 单分组测试 ---")
    single_plain = b'1234567890abcdef' # 恰好16字节
    single_cipher = aes_encrypt_block(single_plain, key)
    print(f"单分组明文: {single_plain.hex()}")
    print(f"单分组密文: {single_cipher.hex()}")
    single_decrypted = aes_decrypt_block(single_cipher, key)
    print(f"单分组解密: {single_decrypted.hex()}")
    print(f"单分组测试是否成功: {single_decrypted == single_plain}")

if __name__ == "__main__":
    main()

运行这段代码,你应该能看到加密解密成功的输出。如果结果不对,请进入下一章的排查环节。

6. 常见问题、调试技巧与性能优化

从零实现一个复杂的算法,调试是不可避免的。以下是我在实现过程中踩过的坑和总结的技巧。

6.1 典型问题与排查清单

当你发现加密解密结果不对时,可以按照以下清单逐项检查:

问题现象 可能原因 排查方法
单分组加解密失败 1. S盒/逆S盒数据错误。
2. 行移位方向错误(加密左移,解密右移)。
3. 列混合系数或计算错误。
4. 轮密钥顺序错误。
1. 打印中间状态矩阵,与标准测试向量对比(如NIST发布的已知答案测试)。
2. 单独测试 sub_bytes shift_rows 等基础函数,输入简单矩阵(如全0x00或全0xFF)看输出是否符合预期。
CBC模式加解密失败,但单分组正常 1. PKCS#7填充/去填充逻辑错误。
2. CBC模式中IV使用错误(加密用前一个密文块,解密用前一个“未解密”的密文块)。
3. 数据长度不是16的倍数。
1. 测试一个恰好16字节(无需填充)的明文,看CBC模式是否正常。
2. 打印每一轮CBC的 prev_block ,确认其值是否正确更新。
解密结果末尾出现乱码 PKCS#7去填充失败,通常是因为填充字节值不正确。 unpad 函数中打印 pad_len data[-pad_len:] ,检查填充字节是否一致。可能是加密端填充或传输过程导致数据损坏。
性能极慢 gf_multiply 函数中使用了低效的循环实现。 使用预计算的查找表来优化有限域乘法,这是性能提升的关键。

6.2 调试技巧:与标准库对比

最可靠的调试方法是与经过验证的标准实现进行对比。我们可以使用Python的 cryptography 库作为参照。

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os

def compare_with_library():
    key = os.urandom(16)
    iv = os.urandom(16)
    plaintext = b'A test message for comparison.'

    # 使用我们的实现
    my_cipher = aes_encrypt_cbc(plaintext, key, iv)
    my_plain = aes_decrypt_cbc(my_cipher, key, iv)

    # 使用cryptography库
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    lib_cipher = encryptor.update(pad(plaintext)) + encryptor.finalize()
    decryptor = cipher.decryptor()
    lib_plain_padded = decryptor.update(lib_cipher) + decryptor.finalize()
    lib_plain = unpad(lib_plain_padded) # 使用我们自己的unpad

    print("密钥:", key.hex())
    print("IV:", iv.hex())
    print("明文:", plaintext)
    print("\n我们的密文:", my_cipher.hex())
    print("库的密文:", lib_cipher.hex())
    print("密文是否一致:", my_cipher == lib_cipher)
    print("\n我们的解密结果:", my_plain)
    print("库的解密结果:", lib_plain)
    print("解密是否一致:", my_plain == lib_plain)

# 注意:运行此函数需要安装cryptography库: pip install cryptography

通过这种对比,可以快速定位问题是出在核心的块加密算法,还是出在CBC模式或填充逻辑上。

6.3 性能优化实战

我们上面实现的 gf_multiply 函数每次乘法都要循环8次,在列混合和密钥扩展中会被调用成千上万次,是性能瓶颈。工业级实现会使用 预计算查找表

优化技巧:使用混合列查找表 我们可以预先计算 gf_multiply(0x02, x) gf_multiply(0x03, x) 的所有可能结果(x从0到255),存储在两个长度为256的数组中。这样,在 mix_columns 中,一次查表就可以代替一次 gf_multiply 调用。

# 预计算查找表
gf_mul_2 = [gf_multiply(0x02, i) for i in range(256)]
gf_mul_3 = [gf_multiply(0x03, i) for i in range(256)]

def mix_columns_fast(state):
    """使用查找表优化的列混合。"""
    new_state = [[0]*4 for _ in range(4)]
    for c in range(4):
        new_state[0][c] = gf_mul_2[state[0][c]] ^ gf_mul_3[state[1][c]] ^ state[2][c] ^ state[3][c]
        new_state[1][c] = state[0][c] ^ gf_mul_2[state[1][c]] ^ gf_mul_3[state[2][c]] ^ state[3][c]
        new_state[2][c] = state[0][c] ^ state[1][c] ^ gf_mul_2[state[2][c]] ^ gf_mul_3[state[3][c]]
        new_state[3][c] = gf_mul_3[state[0][c]] ^ state[1][c] ^ state[2][c] ^ gf_mul_2[state[3][c]]
    return new_state

同理,逆列混合也可以预计算四个查找表(对应系数0x0e, 0x0b, 0x0d, 0x09)。这种“以空间换时间”的策略,是密码学实现中非常经典和有效的优化手段。经过这种优化,我们的Python实现速度可以提升一个数量级,虽然仍远不及C语言或专用指令集的实现,但对于学习和理解算法完全足够,并且能让你体会到真实世界中的优化思路。

从头实现AES就像亲手搭建了一座精密的机械钟表,当你看到每一个齿轮(函数)严丝合缝地转动,最终准确报时(加解密成功)时,那种对算法内在逻辑的透彻理解所带来的成就感,是单纯调用库函数无法比拟的。这个项目带给你的不仅是AES本身,更是面对复杂系统时,如何拆解、实现、调试和优化的一整套工程思维。

更多推荐