PeoplesSquare6:4轮AES积分密码分析完整实战

admin 2025-12-29 01:10:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文解析CTF题目PeoplesSquare6,针对弱化4轮AES实施积分密码分析。内容涵盖程序逆向、AES原理及积分性质传播。文章提供完整攻击代码,演示利用统计特性恢复密钥,并分析攻击复杂度与安全启示,是密码分析领域的优秀实战教程。 综合评分: 100 文章分类: CTF,漏洞分析,逆向分析


cover_image

Peoples Square 6: 4轮AES积分密码分析完整实战

原创

破镜安全

破镜安全

2025年12月27日 08:00 四川

Peoples Square 6: 4轮AES积分密码分析完整实战

前言

本文将深入分析一道来自0CTF的高级密码学挑战题Peoples Square 6。这道题目通过一个弱化的AES实现,为我们提供了一个绝佳的机会来学习积分密码分析(Integral Cryptanalysis)这一重要的密码分析技术。

文章将从零开始,逐步讲解AES加密原理、4轮AES的安全缺陷、积分攻击的理论基础,最终完整实现攻击代码并成功破解密钥。即使是密码学初学者,也能通过本文理解整个攻击过程。

第一部分:题目分析

1.1 题目附件

题目提供了以下文件:

  • crypto300.elf – Linux可执行文件
  • crypto300.exe – Windows可执行文件
  • crypto300.macho – macOS可执行文件
  • output.txt – 程序运行输出(约220KB)

1.2 程序逆向分析

通过逆向工程分析二进制文件,我们可以还原出程序的核心逻辑。程序的工作流程如下:

  1. 生成一个随机的16字节密钥
  2. 获取当前时间戳
  3. 进行1024轮交互式挑战
  4. 如果用户全部猜对,显示加密的flag

每一轮的具体流程:

# 伪代码还原
initTime = time(0)  # 获取时间戳

for i in range(1024):
    # 构造两个明文
    plaintext_0 = [0x00]*8 + encode_int32_le(i) + encode_int32_le(initTime)
    plaintext_1 = [0x01]*8 + encode_int32_le(i) + encode_int32_le(initTime)

    # 使用4轮AES加密
    ciphertext_0 = aes_encrypt_4rounds(plaintext_0, key)
    ciphertext_1 = aes_encrypt_4rounds(plaintext_1, key)

    # 随机选择一个密文显示
    bit = random() % 2
    show_ciphertext = ciphertext_0 if bit == 0 else ciphertext_1

    print(show_ciphertext)
    print("0 or 1?")
    guess = input()

    # 显示两个密文
    print("ciphertext for 0 is:", ciphertext_0)
    print("ciphertext for 1 is:", ciphertext_1)

1.3 关键观察

分析程序逻辑和output.txt文件,我们可以得出以下关键信息:

明文结构:

  • plaintext_0: [00 00 00 00 00 00 00 00] + i的小端序(4字节) + timestamp的小端序(4字节)
  • plaintext_1: [01 01 01 01 01 01 01 01] + i的小端序(4字节) + timestamp的小端序(4字节)

i值的编码(小端序):当i从0到255时,encode_int32_le(i)的结果为:

  • i=0:   [00, 00, 00, 00]
  • i=1:   [01, 00, 00, 00]
  • i=2:   [02, 00, 00, 00]
  • i=255: [FF, 00, 00, 00]

完整的明文格式(前256个):

明文0(i=0):   [00 00 00 00 00 00 00 00 | 00 00 00 00 | t0 t1 t2 t3]
明文0(i=1):   [00 00 00 00 00 00 00 00 | 01 00 00 00 | t0 t1 t2 t3]
明文0(i=2):   [00 00 00 00 00 00 00 00 | 02 00 00 00 | t0 t1 t2 t3]
...
明文0(i=255): [00 00 00 00 00 00 00 00 | FF 00 00 00 | t0 t1 t2 t3]

关键发现:

  1. 字节0-7:常数(对于plaintext_0全是0x00,对于plaintext_1全是0x01)
  2. 字节8:从0x00到0xFF,取遍所有256个可能的值
  3. 字节9-11:常数(都是0x00)
  4. 字节12-15:常数(时间戳,所有明文相同)

这个明文结构为我们实施积分密码分析提供了完美的条件!

1.4 攻击目标

我们的目标是:

  1. 从output.txt中提取1024对密文
  2. 利用前256对密文进行积分密码分析
  3. 恢复16字节的主密钥
  4. 解密加密的flag

第二部分:AES加密原理深度解析

2.1 AES基础知识

AES(Advanced Encryption Standard)是一种对称密钥加密算法,由比利时密码学家Joan Daemen和Vincent Rijmen设计。它在2001年被美国国家标准与技术研究院(NIST)选为加密标准,取代了旧的DES算法。

基本特性:

  • 分组密码,数据块大小:128位(16字节)
  • 密钥长度:128位、192位或256位
  • 本题使用:AES-128(128位密钥)

2.2 AES状态矩阵

AES将16字节的输入数据组织成一个4×4的矩阵,称为”状态”(State)。重要的是,数据按列优先顺序存储:

线性字节数组: [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15]

转换为4x4矩阵(列优先):
    列0  列1  列2  列3
行0  b0   b4   b8   b12
行1  b1   b5   b9   b13
行2  b2   b6   b10  b14
行3  b3   b7   b11  b15

2.3 AES的四种基本操作

2.3.1 SubBytes(字节替换)

使用一个固定的256字节查找表(S-box)对状态矩阵中的每个字节进行替换。

def sub_bytes(state):
    return [sbox[b] for b in state]

S-box特性:

  • 双射(一对一映射):每个输入值对应唯一的输出值
  • 非线性:提供混淆(confusion)
  • 如果输入取所有256个可能的值,输出也取所有256个可能的值(只是顺序不同)

标准AES S-box(部分):

sbox = [
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    # ... 共256个字节
]

2.3.2 ShiftRows(行移位)

对状态矩阵的每一行进行循环左移:

  • 第0行:不移动
  • 第1行:左移1个位置
  • 第2行:左移2个位置
  • 第3行:左移3个位置
原始矩阵:                ShiftRows后:
b0   b4   b8   b12       b0   b4   b8   b12  (第0行不变)
b1   b5   b9   b13   =>  b5   b9   b13  b1   (第1行左移1)
b2   b6   b10  b14       b10  b14  b2   b6   (第2行左移2)
b3   b7   b11  b15       b15  b3   b7   b11  (第3行左移3)

线性数组表示的ShiftRows映射:

def shift_rows(state):
    return [
        state[0],  state[5],  state[10], state[15],
        state[4],  state[9],  state[14], state[3],
        state[8],  state[13], state[2],  state[7],
        state[12], state[1],  state[6],  state[11]
    ]

**作用:**提供扩散(diffusion),将一列中的字节分散到不同的列。

2.3.3 MixColumns(列混合)

对状态矩阵的每一列进行线性变换。每列的4个字节通过矩阵乘法在伽罗瓦域GF(2^8)上进行混合。

变换公式:

输入列: [a0, a1, a2, a3]
输出列: [b0, b1, b2, b3]

b0 = (2•a0) ⊕ (3•a1) ⊕ a2 ⊕ a3
b1 = a0 ⊕ (2•a1) ⊕ (3•a2) ⊕ a3
b2 = a0 ⊕ a1 ⊕ (2•a2) ⊕ (3•a3)
b3 = (3•a0) ⊕ a1 ⊕ a2 ⊕ (2•a3)

其中表示GF(2^8)上的乘法,表示异或。

def mix_columns(state):
    result = []
    for i in range(4):  # 对每一列
        col = [state[i], state[i+4], state[i+8], state[i+12]]
        mixed = mix_single_column(col)
        result.extend([mixed[0], mixed[1], mixed[2], mixed[3]])

    # 重新排列为列优先
    return [result[i] for i in [0,4,8,12, 1,5,9,13, 2,6,10,14, 3,7,11,15]]

def mix_single_column(col):
    return [
        gf_mult(2, col[0]) ^ gf_mult(3, col[1]) ^ col[2] ^ col[3],
        col[0] ^ gf_mult(2, col[1]) ^ gf_mult(3, col[2]) ^ col[3],
        col[0] ^ col[1] ^ gf_mult(2, col[2]) ^ gf_mult(3, col[3]),
        gf_mult(3, col[0]) ^ col[1] ^ col[2] ^ gf_mult(2, col[3])
    ]

关键性质:

  • MixColumns只混合同一列内的字节
  • 不同列之间相互独立
  • 这个性质在积分攻击中非常重要

2.3.4 AddRoundKey(轮密钥加)

将当前轮的16字节轮密钥与状态矩阵进行按位异或。

def add_round_key(state, round_key):
    return [state[i] ^ round_key[i] for i in range(16)]

特性:

  • 简单快速
  • 可逆(异或两次恢复原值)
  • 提供密钥扩散

2.4 密钥扩展算法

AES-128使用128位主密钥生成11个轮密钥(每个16字节):

  • 轮密钥0:初始轮密钥(等于主密钥)
  • 轮密钥1-10:用于10轮加密
def key_expansion(master_key):
    # 将密钥分成4个字(每个字4字节)
    w = []
    for i in range(4):
        w.append([master_key[4*i], master_key[4*i+1],
                  master_key[4*i+2], master_key[4*i+3]])

    # 扩展到44个字(11个轮密钥)
    for i in range(4, 44):
        temp = w[i-1][:]
        if i % 4 == 0:
            # RotWord: 循环左移
            temp = temp[1:] + temp[:1]
            # SubWord: 应用S-box
            temp = [sbox[b] for b in temp]
            # 异或轮常数
            temp[0] ^= Rcon[i//4 - 1]

        w.append([w[i-4][j] ^ temp[j] for j in range(4)])

    # 将44个字转换为11个轮密钥
    round_keys = []
    for i in range(11):
        rk = []
        for j in range(4):
            rk.extend(w[i*4 + j])
        round_keys.append(rk)

    return round_keys

轮常数Rcon:

Rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]

2.5 标准AES-128加密流程

标准的AES-128包含10轮加密:

def aes_encrypt_128(plaintext, key):
    round_keys = key_expansion(key)
    state = add_round_key(plaintext, round_keys[0])  # 初始轮密钥加

    for round in range(1, 10):  # 第1-9轮
        state = sub_bytes(state)
        state = shift_rows(state)
        state = mix_columns(state)
        state = add_round_key(state, round_keys[round])

    # 第10轮(最后一轮,无MixColumns)
    state = sub_bytes(state)
    state = shift_rows(state)
    state = add_round_key(state, round_keys[10])

    return state

2.6 本题的4轮AES

本题使用的是弱化版本,只有4轮:

def aes_encrypt_4rounds(plaintext, key):
    round_keys = key_expansion(key)
    state = add_round_key(plaintext, round_keys[0])  # 初始轮密钥加

    for round in range(1, 4):  # 第1-3轮(标准轮)
        state = sub_bytes(state)
        state = shift_rows(state)
        state = mix_columns(state)
        state = add_round_key(state, round_keys[round])

    # 第4轮(最后一轮,无MixColumns)
    state = sub_bytes(state)
    state = shift_rows(state)
    state = add_round_key(state, round_keys[4])

    return state

为什么4轮不安全?

标准AES-128使用10轮是经过精心设计的。轮数太少会导致:

  1. 扩散不充分:明文的局部变化不能影响到整个密文
  2. 混淆不够:统计特性容易被追踪
  3. 容易受到各种密码分析攻击

4轮AES正好落在积分攻击的有效范围内!

第三部分:积分密码分析理论

3.1 什么是积分密码分析

积分密码分析(Integral Cryptanalysis),也称为Square攻击,是由Lars Knudsen和David Wagner等密码学家发展的一种针对分组密码的选择明文攻击方法。

核心思想:通过精心选择一组明文(称为积分集),使得这组明文在经过部分轮加密后,某些字节位置呈现特定的统计性质。利用这些性质,可以逐字节恢复最后一轮的轮密钥。

3.2 积分集的性质

在积分攻击中,我们关注字节的两种属性:

3.2.1 常数(Constant,记为C)

对于明文集合中的所有明文,该字节位置的值都相同。

示例:
明文1: [AA, ...]
明文2: [AA, ...]
明文3: [AA, ...]
...
明文256: [AA, ...]

第0个字节都是0xAA,记为C

3.2.2 全取(All,记为A)

该字节位置取遍所有256个可能的值(0x00到0xFF)。

示例:
明文1: [00, ...]
明文2: [01, ...]
明文3: [02, ...]
...
明文256: [FF, ...]

第0个字节取遍所有值,记为A

A的关键性质:

如果一个字节是A(取所有可能的值),那么将这256个值异或起来,结果一定是0:

0x00 ⊕ 0x01 ⊕ 0x02 ⊕ ... ⊕ 0xFE ⊕ 0xFF = 0x00

这是因为每个比特位在256个值中出现奇数次(128次为1,128次为0),异或的结果为0。

3.3 积分性质的传播

现在我们追踪积分性质在AES轮函数中的传播。

3.3.1 本题的初始积分集

根据前面的分析,本题前256个明文的积分性质为:

字节位置: 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
性质:     C  C  C  C  C  C  C  C  A  C  C  C  C  C  C  C

转换为AES状态矩阵(列优先):

    列0  列1  列2  列3
行0  C    C    A    C
行1  C    C    C    C
行2  C    C    C    C
行3  C    C    C    C

3.3.2 第0轮(AddRoundKey)

操作前:  C  C  A  C  |  C  C  C  C  |  C  C  C  C  |  C  C  C  C
         ⊕  ⊕  ⊕  ⊕     ⊕  ⊕  ⊕  ⊕     ⊕  ⊕  ⊕  ⊕     ⊕  ⊕  ⊕  ⊕
轮密钥:  k  k  k  k  |  k  k  k  k  |  k  k  k  k  |  k  k  k  k
操作后:  C  C  A  C  |  C  C  C  C  |  C  C  C  C  |  C  C  C  C

结论: C ⊕ k = C(新常数),A ⊕ k = A(仍然全取),性质保持。

3.3.3 第1轮

步骤1:SubBytes

操作前:  C  C  A  C  |  C  C  C  C  |  C  C  C  C  |  C  C  C  C
         ↓  ↓  ↓  ↓     ↓  ↓  ↓  ↓     ↓  ↓  ↓  ↓     ↓  ↓  ↓  ↓
操作后:  C  C  A  C  |  C  C  C  C  |  C  C  C  C  |  C  C  C  C

S-box是双射,所以C仍是C,A仍是A。

步骤2:ShiftRows

操作前矩阵:
C  C  A  C
C  C  C  C
C  C  C  C
C  C  C  C

操作后矩阵:
C  C  A  C    (第0行不变)
C  C  C  C    (第1行左移1)
C  C  C  C    (第2行左移2)
C  C  C  C    (第3行左移3)

对于我们的case,A在第2列第0行,ShiftRows后仍在第2列第0行,性质不变。

步骤3:MixColumns

这是关键步骤!MixColumns对每一列独立处理。

  • 第0列:全是C,混合后仍然全是C
  • 第1列:全是C,混合后仍然全是C
  • 第2列:有一个A和三个C

对于第2列 [A, C, C, C],经过MixColumns变换:

b0 = (2•A) ⊕ (3•C) ⊕ C ⊕ C = (2•A) ⊕ C'
b1 = A ⊕ (2•C) ⊕ (3•C) ⊕ C = A ⊕ C'
b2 = A ⊕ C ⊕ (2•C) ⊕ (3•C) = A ⊕ C'
b3 = (3•A) ⊕ C ⊕ C ⊕ (2•C) = (3•A) ⊕ C'

由于A取所有可能的值,2•A、3•A也取所有可能的值(GF(2^8)上的乘法是双射)。因此:

  • b0 = (2•A) ⊕ C’ 仍然是A
  • b1 = A ⊕ C’ 仍然是A
  • b2 = A ⊕ C’ 仍然是A
  • b3 = (3•A) ⊕ C’ 仍然是A

结论:第2列的所有4个字节都变成A!

MixColumns后矩阵:
C  C  A  C
C  C  A  C
C  C  A  C
C  C  A  C

步骤4:AddRoundKey

性质保持不变。

3.3.4 第1轮结束状态

线性表示: C C C C | C C C C | A A A A | C C C C
矩阵表示:
C  C  A  C
C  C  A  C
C  C  A  C
C  C  A  C

**关键观察:**一列变成了全A!

3.3.5 第2轮

继续追踪…

SubBytes: 性质不变

C  C  A  C
C  C  A  C
C  C  A  C
C  C  A  C

ShiftRows:

原始矩阵:
C  C  A  C
C  C  A  C
C  C  A  C
C  C  A  C

ShiftRows后:
C  C  A  C    (第0行)
C  A  C  C    (第1行左移1)
A  C  C  C    (第2行左移2)
C  C  C  A    (第3行左移3)

现在每一列都有一个A!

MixColumns:

每一列都是 [?, A, ?, ?] 或类似的模式(至少有一个A)。根据MixColumns的性质,只要一列中有一个A,混合后整列都变成A。

MixColumns后:
A  A  A  A
A  A  A  A
A  A  A  A
A  A  A  A

所有16个字节都变成A!

AddRoundKey: 性质保持

3.3.6 第2轮结束状态

全部为A: A A A A | A A A A | A A A A | A A A A

3.3.7 第3轮

由于输入全是A,经过SubBytes、ShiftRows、MixColumns、AddRoundKey后,输出仍然全是A。

3.3.8 第3轮结束状态

全部为A: A A A A | A A A A | A A A A | A A A A

3.4 攻击第4轮(最后一轮)

第4轮(最后一轮)的操作:SubBytes -> ShiftRows -> AddRoundKey(无MixColumns)

第3轮结束: A A A A | A A A A | A A A A | A A A A
    ↓ SubBytes (A仍是A,因为S-box是双射)
           A A A A | A A A A | A A A A | A A A A
    ↓ ShiftRows (只是重排,仍然全是A)
           A A A A | A A A A | A A A A | A A A A
    ↓ AddRoundKey (异或轮密钥4)
密文:      c c c c | c c c c | c c c c | c c c c

对于密文的第i个字节:

ciphertext[i] = state_after_shiftrows[i] ⊕ round_key_4[i]

其中 state_after_shiftrows[i] 在256个不同的明文下取所有可能的值(因为是A)。

3.5 恢复轮密钥的方法

对于每个字节位置i(i=0到15),我们尝试所有256个可能的密钥字节值:

for candidate_key_byte in range(256):
    xor_sum = 0
    for ciph in ciphertexts[:256]:
        # 逆向一步:去除AddRoundKey和SubBytes
        temp = ciph[i] ^ candidate_key_byte  # 去除AddRoundKey
        temp = inv_sbox[temp]                # 去除SubBytes
        xor_sum ^= temp

    if xor_sum == 0:
        # candidate_key_byte是一个候选值
        candidates.append(candidate_key_byte)

原理:

如果candidate_key_byte是正确的密钥字节,那么inv_sbox[ciph[i] ^ candidate_key_byte]会恢复到ShiftRows之后、SubBytes之前的状态。由于第3轮结束时该位置取所有可能的值(A性质),经过ShiftRows后仍然取所有可能的值,因此异或和为0。

如果candidate_key_byte是错误的,异或和通常不为0(虽然有1/256的概率偶然为0,所以可能有假阳性)。

3.6 从第4轮密钥恢复主密钥

一旦我们恢复了第4轮的轮密钥,就可以逆向密钥扩展算法来恢复主密钥。

密钥扩展的正向公式:

w[i] = w[i-4] ⊕ f(w[i-1])    当 i % 4 == 0
w[i] = w[i-4] ⊕ w[i-1]        否则

逆向公式:

w[i-4] = w[i] ⊕ f(w[i-1])    当 i % 4 == 0
w[i-4] = w[i] ⊕ w[i-1]        否则

通过从w[16:20](第4轮密钥)逆推到w[0:4](主密钥),即可完成密钥恢复。

第四部分:完整攻击实现

4.1 步骤1:提取密文数据

首先,我们需要从output.txt中提取密文数据。

# extract_ciphertexts.py
def parse_output(filename):
    """
    从output.txt中提取所有密文对

    返回:
        ciphertexts_0: ciphertext for 0 的列表
        ciphertexts_1: ciphertext for 1 的列表
    """
    with open(filename, 'r') as f:
        lines = f.readlines()

    ciphertexts_0 = []
    ciphertexts_1 = []

    i = 0
&nbsp; &nbsp;&nbsp;while&nbsp;i < len(lines):
&nbsp; &nbsp; &nbsp; &nbsp; line = lines[i].strip()

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;line ==&nbsp;"ciphertext for 0 is:":
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;i < len(lines):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hex_data = lines[i].strip()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bytes_list = [int(x,&nbsp;16)&nbsp;for&nbsp;x&nbsp;in&nbsp;hex_data.split()]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ciphertexts_0.append(bytes_list)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;line ==&nbsp;"ciphertext for 1 is:":
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;i < len(lines):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hex_data = lines[i].strip()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bytes_list = [int(x,&nbsp;16)&nbsp;for&nbsp;x&nbsp;in&nbsp;hex_data.split()]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ciphertexts_1.append(bytes_list)

&nbsp; &nbsp; &nbsp; &nbsp; i +=&nbsp;1

&nbsp; &nbsp;&nbsp;return&nbsp;ciphertexts_0, ciphertexts_1

测试:

c0, c1 = parse_output("output.txt")
print(f"提取了&nbsp;{len(c0)}&nbsp;个 ciphertext for 0")
print(f"提取了&nbsp;{len(c1)}&nbsp;个 ciphertext for 1")
print(f"\n第一个密文对:")
print(f"C0[0]:&nbsp;{' '.join(f'{b:02x}'&nbsp;for&nbsp;b&nbsp;in&nbsp;c0[0])}")
print(f"C1[0]:&nbsp;{' '.join(f'{b:02x}'&nbsp;for&nbsp;b&nbsp;in&nbsp;c1[0])}")

4.2 步骤2:实现AES基础组件

实现完整的AES组件,包括S-box、逆S-box、ShiftRows、MixColumns等。

# aes.py
# 标准AES S-box
sbox = [
&nbsp; &nbsp;&nbsp;0x63,&nbsp;0x7c,&nbsp;0x77,&nbsp;0x7b,&nbsp;0xf2,&nbsp;0x6b,&nbsp;0x6f,&nbsp;0xc5,
&nbsp; &nbsp;&nbsp;0x30,&nbsp;0x01,&nbsp;0x67,&nbsp;0x2b,&nbsp;0xfe,&nbsp;0xd7,&nbsp;0xab,&nbsp;0x76,
&nbsp; &nbsp;&nbsp;0xca,&nbsp;0x82,&nbsp;0xc9,&nbsp;0x7d,&nbsp;0xfa,&nbsp;0x59,&nbsp;0x47,&nbsp;0xf0,
&nbsp; &nbsp;&nbsp;0xad,&nbsp;0xd4,&nbsp;0xa2,&nbsp;0xaf,&nbsp;0x9c,&nbsp;0xa4,&nbsp;0x72,&nbsp;0xc0,
&nbsp; &nbsp;&nbsp;0xb7,&nbsp;0xfd,&nbsp;0x93,&nbsp;0x26,&nbsp;0x36,&nbsp;0x3f,&nbsp;0xf7,&nbsp;0xcc,
&nbsp; &nbsp;&nbsp;0x34,&nbsp;0xa5,&nbsp;0xe5,&nbsp;0xf1,&nbsp;0x71,&nbsp;0xd8,&nbsp;0x31,&nbsp;0x15,
&nbsp; &nbsp;&nbsp;0x04,&nbsp;0xc7,&nbsp;0x23,&nbsp;0xc3,&nbsp;0x18,&nbsp;0x96,&nbsp;0x05,&nbsp;0x9a,
&nbsp; &nbsp;&nbsp;0x07,&nbsp;0x12,&nbsp;0x80,&nbsp;0xe2,&nbsp;0xeb,&nbsp;0x27,&nbsp;0xb2,&nbsp;0x75,
&nbsp; &nbsp;&nbsp;0x09,&nbsp;0x83,&nbsp;0x2c,&nbsp;0x1a,&nbsp;0x1b,&nbsp;0x6e,&nbsp;0x5a,&nbsp;0xa0,
&nbsp; &nbsp;&nbsp;0x52,&nbsp;0x3b,&nbsp;0xd6,&nbsp;0xb3,&nbsp;0x29,&nbsp;0xe3,&nbsp;0x2f,&nbsp;0x84,
&nbsp; &nbsp;&nbsp;0x53,&nbsp;0xd1,&nbsp;0x00,&nbsp;0xed,&nbsp;0x20,&nbsp;0xfc,&nbsp;0xb1,&nbsp;0x5b,
&nbsp; &nbsp;&nbsp;0x6a,&nbsp;0xcb,&nbsp;0xbe,&nbsp;0x39,&nbsp;0x4a,&nbsp;0x4c,&nbsp;0x58,&nbsp;0xcf,
&nbsp; &nbsp;&nbsp;0xd0,&nbsp;0xef,&nbsp;0xaa,&nbsp;0xfb,&nbsp;0x43,&nbsp;0x4d,&nbsp;0x33,&nbsp;0x85,
&nbsp; &nbsp;&nbsp;0x45,&nbsp;0xf9,&nbsp;0x02,&nbsp;0x7f,&nbsp;0x50,&nbsp;0x3c,&nbsp;0x9f,&nbsp;0xa8,
&nbsp; &nbsp;&nbsp;0x51,&nbsp;0xa3,&nbsp;0x40,&nbsp;0x8f,&nbsp;0x92,&nbsp;0x9d,&nbsp;0x38,&nbsp;0xf5,
&nbsp; &nbsp;&nbsp;0xbc,&nbsp;0xb6,&nbsp;0xda,&nbsp;0x21,&nbsp;0x10,&nbsp;0xff,&nbsp;0xf3,&nbsp;0xd2,
&nbsp; &nbsp;&nbsp;0xcd,&nbsp;0x0c,&nbsp;0x13,&nbsp;0xec,&nbsp;0x5f,&nbsp;0x97,&nbsp;0x44,&nbsp;0x17,
&nbsp; &nbsp;&nbsp;0xc4,&nbsp;0xa7,&nbsp;0x7e,&nbsp;0x3d,&nbsp;0x64,&nbsp;0x5d,&nbsp;0x19,&nbsp;0x73,
&nbsp; &nbsp;&nbsp;0x60,&nbsp;0x81,&nbsp;0x4f,&nbsp;0xdc,&nbsp;0x22,&nbsp;0x2a,&nbsp;0x90,&nbsp;0x88,
&nbsp; &nbsp;&nbsp;0x46,&nbsp;0xee,&nbsp;0xb8,&nbsp;0x14,&nbsp;0xde,&nbsp;0x5e,&nbsp;0x0b,&nbsp;0xdb,
&nbsp; &nbsp;&nbsp;0xe0,&nbsp;0x32,&nbsp;0x3a,&nbsp;0x0a,&nbsp;0x49,&nbsp;0x06,&nbsp;0x24,&nbsp;0x5c,
&nbsp; &nbsp;&nbsp;0xc2,&nbsp;0xd3,&nbsp;0xac,&nbsp;0x62,&nbsp;0x91,&nbsp;0x95,&nbsp;0xe4,&nbsp;0x79,
&nbsp; &nbsp;&nbsp;0xe7,&nbsp;0xc8,&nbsp;0x37,&nbsp;0x6d,&nbsp;0x8d,&nbsp;0xd5,&nbsp;0x4e,&nbsp;0xa9,
&nbsp; &nbsp;&nbsp;0x6c,&nbsp;0x56,&nbsp;0xf4,&nbsp;0xea,&nbsp;0x65,&nbsp;0x7a,&nbsp;0xae,&nbsp;0x08,
&nbsp; &nbsp;&nbsp;0xba,&nbsp;0x78,&nbsp;0x25,&nbsp;0x2e,&nbsp;0x1c,&nbsp;0xa6,&nbsp;0xb4,&nbsp;0xc6,
&nbsp; &nbsp;&nbsp;0xe8,&nbsp;0xdd,&nbsp;0x74,&nbsp;0x1f,&nbsp;0x4b,&nbsp;0xbd,&nbsp;0x8b,&nbsp;0x8a,
&nbsp; &nbsp;&nbsp;0x70,&nbsp;0x3e,&nbsp;0xb5,&nbsp;0x66,&nbsp;0x48,&nbsp;0x03,&nbsp;0xf6,&nbsp;0x0e,
&nbsp; &nbsp;&nbsp;0x61,&nbsp;0x35,&nbsp;0x57,&nbsp;0xb9,&nbsp;0x86,&nbsp;0xc1,&nbsp;0x1d,&nbsp;0x9e,
&nbsp; &nbsp;&nbsp;0xe1,&nbsp;0xf8,&nbsp;0x98,&nbsp;0x11,&nbsp;0x69,&nbsp;0xd9,&nbsp;0x8e,&nbsp;0x94,
&nbsp; &nbsp;&nbsp;0x9b,&nbsp;0x1e,&nbsp;0x87,&nbsp;0xe9,&nbsp;0xce,&nbsp;0x55,&nbsp;0x28,&nbsp;0xdf,
&nbsp; &nbsp;&nbsp;0x8c,&nbsp;0xa1,&nbsp;0x89,&nbsp;0x0d,&nbsp;0xbf,&nbsp;0xe6,&nbsp;0x42,&nbsp;0x68,
&nbsp; &nbsp;&nbsp;0x41,&nbsp;0x99,&nbsp;0x2d,&nbsp;0x0f,&nbsp;0xb0,&nbsp;0x54,&nbsp;0xbb,&nbsp;0x16
]

# 逆S-box
inv_sbox = [
&nbsp; &nbsp;&nbsp;0x52,&nbsp;0x09,&nbsp;0x6a,&nbsp;0xd5,&nbsp;0x30,&nbsp;0x36,&nbsp;0xa5,&nbsp;0x38,
&nbsp; &nbsp;&nbsp;0xbf,&nbsp;0x40,&nbsp;0xa3,&nbsp;0x9e,&nbsp;0x81,&nbsp;0xf3,&nbsp;0xd7,&nbsp;0xfb,
&nbsp; &nbsp;&nbsp;0x7c,&nbsp;0xe3,&nbsp;0x39,&nbsp;0x82,&nbsp;0x9b,&nbsp;0x2f,&nbsp;0xff,&nbsp;0x87,
&nbsp; &nbsp;&nbsp;0x34,&nbsp;0x8e,&nbsp;0x43,&nbsp;0x44,&nbsp;0xc4,&nbsp;0xde,&nbsp;0xe9,&nbsp;0xcb,
&nbsp; &nbsp;&nbsp;0x54,&nbsp;0x7b,&nbsp;0x94,&nbsp;0x32,&nbsp;0xa6,&nbsp;0xc2,&nbsp;0x23,&nbsp;0x3d,
&nbsp; &nbsp;&nbsp;0xee,&nbsp;0x4c,&nbsp;0x95,&nbsp;0x0b,&nbsp;0x42,&nbsp;0xfa,&nbsp;0xc3,&nbsp;0x4e,
&nbsp; &nbsp;&nbsp;0x08,&nbsp;0x2e,&nbsp;0xa1,&nbsp;0x66,&nbsp;0x28,&nbsp;0xd9,&nbsp;0x24,&nbsp;0xb2,
&nbsp; &nbsp;&nbsp;0x76,&nbsp;0x5b,&nbsp;0xa2,&nbsp;0x49,&nbsp;0x6d,&nbsp;0x8b,&nbsp;0xd1,&nbsp;0x25,
&nbsp; &nbsp;&nbsp;0x72,&nbsp;0xf8,&nbsp;0xf6,&nbsp;0x64,&nbsp;0x86,&nbsp;0x68,&nbsp;0x98,&nbsp;0x16,
&nbsp; &nbsp;&nbsp;0xd4,&nbsp;0xa4,&nbsp;0x5c,&nbsp;0xcc,&nbsp;0x5d,&nbsp;0x65,&nbsp;0xb6,&nbsp;0x92,
&nbsp; &nbsp;&nbsp;0x6c,&nbsp;0x70,&nbsp;0x48,&nbsp;0x50,&nbsp;0xfd,&nbsp;0xed,&nbsp;0xb9,&nbsp;0xda,
&nbsp; &nbsp;&nbsp;0x5e,&nbsp;0x15,&nbsp;0x46,&nbsp;0x57,&nbsp;0xa7,&nbsp;0x8d,&nbsp;0x9d,&nbsp;0x84,
&nbsp; &nbsp;&nbsp;0x90,&nbsp;0xd8,&nbsp;0xab,&nbsp;0x00,&nbsp;0x8c,&nbsp;0xbc,&nbsp;0xd3,&nbsp;0x0a,
&nbsp; &nbsp;&nbsp;0xf7,&nbsp;0xe4,&nbsp;0x58,&nbsp;0x05,&nbsp;0xb8,&nbsp;0xb3,&nbsp;0x45,&nbsp;0x06,
&nbsp; &nbsp;&nbsp;0xd0,&nbsp;0x2c,&nbsp;0x1e,&nbsp;0x8f,&nbsp;0xca,&nbsp;0x3f,&nbsp;0x0f,&nbsp;0x02,
&nbsp; &nbsp;&nbsp;0xc1,&nbsp;0xaf,&nbsp;0xbd,&nbsp;0x03,&nbsp;0x01,&nbsp;0x13,&nbsp;0x8a,&nbsp;0x6b,
&nbsp; &nbsp;&nbsp;0x3a,&nbsp;0x91,&nbsp;0x11,&nbsp;0x41,&nbsp;0x4f,&nbsp;0x67,&nbsp;0xdc,&nbsp;0xea,
&nbsp; &nbsp;&nbsp;0x97,&nbsp;0xf2,&nbsp;0xcf,&nbsp;0xce,&nbsp;0xf0,&nbsp;0xb4,&nbsp;0xe6,&nbsp;0x73,
&nbsp; &nbsp;&nbsp;0x96,&nbsp;0xac,&nbsp;0x74,&nbsp;0x22,&nbsp;0xe7,&nbsp;0xad,&nbsp;0x35,&nbsp;0x85,
&nbsp; &nbsp;&nbsp;0xe2,&nbsp;0xf9,&nbsp;0x37,&nbsp;0xe8,&nbsp;0x1c,&nbsp;0x75,&nbsp;0xdf,&nbsp;0x6e,
&nbsp; &nbsp;&nbsp;0x47,&nbsp;0xf1,&nbsp;0x1a,&nbsp;0x71,&nbsp;0x1d,&nbsp;0x29,&nbsp;0xc5,&nbsp;0x89,
&nbsp; &nbsp;&nbsp;0x6f,&nbsp;0xb7,&nbsp;0x62,&nbsp;0x0e,&nbsp;0xaa,&nbsp;0x18,&nbsp;0xbe,&nbsp;0x1b,
&nbsp; &nbsp;&nbsp;0xfc,&nbsp;0x56,&nbsp;0x3e,&nbsp;0x4b,&nbsp;0xc6,&nbsp;0xd2,&nbsp;0x79,&nbsp;0x20,
&nbsp; &nbsp;&nbsp;0x9a,&nbsp;0xdb,&nbsp;0xc0,&nbsp;0xfe,&nbsp;0x78,&nbsp;0xcd,&nbsp;0x5a,&nbsp;0xf4,
&nbsp; &nbsp;&nbsp;0x1f,&nbsp;0xdd,&nbsp;0xa8,&nbsp;0x33,&nbsp;0x88,&nbsp;0x07,&nbsp;0xc7,&nbsp;0x31,
&nbsp; &nbsp;&nbsp;0xb1,&nbsp;0x12,&nbsp;0x10,&nbsp;0x59,&nbsp;0x27,&nbsp;0x80,&nbsp;0xec,&nbsp;0x5f,
&nbsp; &nbsp;&nbsp;0x60,&nbsp;0x51,&nbsp;0x7f,&nbsp;0xa9,&nbsp;0x19,&nbsp;0xb5,&nbsp;0x4a,&nbsp;0x0d,
&nbsp; &nbsp;&nbsp;0x2d,&nbsp;0xe5,&nbsp;0x7a,&nbsp;0x9f,&nbsp;0x93,&nbsp;0xc9,&nbsp;0x9c,&nbsp;0xef,
&nbsp; &nbsp;&nbsp;0xa0,&nbsp;0xe0,&nbsp;0x3b,&nbsp;0x4d,&nbsp;0xae,&nbsp;0x2a,&nbsp;0xf5,&nbsp;0xb0,
&nbsp; &nbsp;&nbsp;0xc8,&nbsp;0xeb,&nbsp;0xbb,&nbsp;0x3c,&nbsp;0x83,&nbsp;0x53,&nbsp;0x99,&nbsp;0x61,
&nbsp; &nbsp;&nbsp;0x17,&nbsp;0x2b,&nbsp;0x04,&nbsp;0x7e,&nbsp;0xba,&nbsp;0x77,&nbsp;0xd6,&nbsp;0x26,
&nbsp; &nbsp;&nbsp;0xe1,&nbsp;0x69,&nbsp;0x14,&nbsp;0x63,&nbsp;0x55,&nbsp;0x21,&nbsp;0x0c,&nbsp;0x7d
]

def&nbsp;sub_bytes(state):
&nbsp; &nbsp;&nbsp;return&nbsp;[sbox[b]&nbsp;for&nbsp;b&nbsp;in&nbsp;state]

def&nbsp;inv_sub_bytes(state):
&nbsp; &nbsp;&nbsp;return&nbsp;[inv_sbox[b]&nbsp;for&nbsp;b&nbsp;in&nbsp;state]

def&nbsp;shift_rows(state):
&nbsp; &nbsp;&nbsp;return&nbsp;[
&nbsp; &nbsp; &nbsp; &nbsp; state[0], state[5], state[10], state[15],
&nbsp; &nbsp; &nbsp; &nbsp; state[4], state[9], state[14], state[3],
&nbsp; &nbsp; &nbsp; &nbsp; state[8], state[13], state[2], state[7],
&nbsp; &nbsp; &nbsp; &nbsp; state[12], state[1], state[6], state[11]
&nbsp; &nbsp; ]

def&nbsp;inv_shift_rows(state):
&nbsp; &nbsp;&nbsp;return&nbsp;[
&nbsp; &nbsp; &nbsp; &nbsp; state[0], state[13], state[10], state[7],
&nbsp; &nbsp; &nbsp; &nbsp; state[4], state[1], state[14], state[11],
&nbsp; &nbsp; &nbsp; &nbsp; state[8], state[5], state[2], state[15],
&nbsp; &nbsp; &nbsp; &nbsp; state[12], state[9], state[6], state[3]
&nbsp; &nbsp; ]

def&nbsp;gf_mult(a, b):
&nbsp; &nbsp;&nbsp;"""GF(2^8)乘法"""
&nbsp; &nbsp; p =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;in&nbsp;range(8):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;b &&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p ^= a
&nbsp; &nbsp; &nbsp; &nbsp; hi_bit_set = a &&nbsp;0x80
&nbsp; &nbsp; &nbsp; &nbsp; a = (a <<&nbsp;1) &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;hi_bit_set:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a ^=&nbsp;0x1b
&nbsp; &nbsp; &nbsp; &nbsp; b >>=&nbsp;1
&nbsp; &nbsp;&nbsp;return&nbsp;p

def&nbsp;mix_single_column(col):
&nbsp; &nbsp;&nbsp;return&nbsp;[
&nbsp; &nbsp; &nbsp; &nbsp; gf_mult(2, col[0]) ^ gf_mult(3, col[1]) ^ col[2] ^ col[3],
&nbsp; &nbsp; &nbsp; &nbsp; col[0] ^ gf_mult(2, col[1]) ^ gf_mult(3, col[2]) ^ col[3],
&nbsp; &nbsp; &nbsp; &nbsp; col[0] ^ col[1] ^ gf_mult(2, col[2]) ^ gf_mult(3, col[3]),
&nbsp; &nbsp; &nbsp; &nbsp; gf_mult(3, col[0]) ^ col[1] ^ col[2] ^ gf_mult(2, col[3])
&nbsp; &nbsp; ]

def&nbsp;mix_columns(state):
&nbsp; &nbsp; result = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(4):
&nbsp; &nbsp; &nbsp; &nbsp; column = [state[i], state[i+4], state[i+8], state[i+12]]
&nbsp; &nbsp; &nbsp; &nbsp; mixed = mix_single_column(column)
&nbsp; &nbsp; &nbsp; &nbsp; result.append(mixed)

&nbsp; &nbsp;&nbsp;return&nbsp;[result[0][0], result[0][1], result[0][2], result[0][3],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result[1][0], result[1][1], result[1][2], result[1][3],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result[2][0], result[2][1], result[2][2], result[2][3],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result[3][0], result[3][1], result[3][2], result[3][3]]

def&nbsp;add_round_key(state, round_key):
&nbsp; &nbsp;&nbsp;return&nbsp;[state[i] ^ round_key[i]&nbsp;for&nbsp;i&nbsp;in&nbsp;range(16)]

def&nbsp;key_expansion(key):
&nbsp; &nbsp;&nbsp;"""密钥扩展:从16字节主密钥生成5个轮密钥(4轮AES需要5个)"""
&nbsp; &nbsp; Rcon = [0x01,&nbsp;0x02,&nbsp;0x04,&nbsp;0x08,&nbsp;0x10]

&nbsp; &nbsp; w = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(4):
&nbsp; &nbsp; &nbsp; &nbsp; w.append([key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]])

&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(4,&nbsp;20):
&nbsp; &nbsp; &nbsp; &nbsp; temp = w[i-1][:]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;i %&nbsp;4&nbsp;==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp = temp[1:] + temp[:1]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp = [sbox[b]&nbsp;for&nbsp;b&nbsp;in&nbsp;temp]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp[0] ^= Rcon[i//4&nbsp;-&nbsp;1]
&nbsp; &nbsp; &nbsp; &nbsp; w.append([w[i-4][j] ^ temp[j]&nbsp;for&nbsp;j&nbsp;in&nbsp;range(4)])

&nbsp; &nbsp; round_keys = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(5):
&nbsp; &nbsp; &nbsp; &nbsp; rk = []
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;in&nbsp;range(4):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rk.extend(w[i*4&nbsp;+ j])
&nbsp; &nbsp; &nbsp; &nbsp; round_keys.append(rk)

&nbsp; &nbsp;&nbsp;return&nbsp;round_keys

4.3 步骤3:实现4轮AES加密和解密

# aes_4rounds.py
from&nbsp;aes&nbsp;import&nbsp;*

def&nbsp;aes_encrypt_4rounds(plaintext, key):
&nbsp; &nbsp;&nbsp;"""4轮AES加密"""
&nbsp; &nbsp; round_keys = key_expansion(key)

&nbsp; &nbsp;&nbsp;# 初始轮密钥加
&nbsp; &nbsp; state = add_round_key(plaintext, round_keys[0])

&nbsp; &nbsp;&nbsp;# 第1-3轮(标准轮)
&nbsp; &nbsp;&nbsp;for&nbsp;round_num&nbsp;in&nbsp;range(1,&nbsp;4):
&nbsp; &nbsp; &nbsp; &nbsp; state = sub_bytes(state)
&nbsp; &nbsp; &nbsp; &nbsp; state = shift_rows(state)
&nbsp; &nbsp; &nbsp; &nbsp; state = mix_columns(state)
&nbsp; &nbsp; &nbsp; &nbsp; state = add_round_key(state, round_keys[round_num])

&nbsp; &nbsp;&nbsp;# 第4轮(最后一轮,无MixColumns)
&nbsp; &nbsp; state = sub_bytes(state)
&nbsp; &nbsp; state = shift_rows(state)
&nbsp; &nbsp; state = add_round_key(state, round_keys[4])

&nbsp; &nbsp;&nbsp;return&nbsp;state

def&nbsp;aes_decrypt_4rounds(ciphertext, key):
&nbsp; &nbsp;&nbsp;"""4轮AES解密"""
&nbsp; &nbsp; round_keys = key_expansion(key)

&nbsp; &nbsp;&nbsp;# 逆向第4轮
&nbsp; &nbsp; state = add_round_key(ciphertext, round_keys[4])
&nbsp; &nbsp; state = inv_shift_rows(state)
&nbsp; &nbsp; state = inv_sub_bytes(state)

&nbsp; &nbsp;&nbsp;# 逆向第3-1轮
&nbsp; &nbsp;&nbsp;for&nbsp;round_num&nbsp;in&nbsp;range(3,&nbsp;0,&nbsp;-1):
&nbsp; &nbsp; &nbsp; &nbsp; state = add_round_key(state, round_keys[round_num])
&nbsp; &nbsp; &nbsp; &nbsp; state = inv_mix_columns(state)
&nbsp; &nbsp; &nbsp; &nbsp; state = inv_shift_rows(state)
&nbsp; &nbsp; &nbsp; &nbsp; state = inv_sub_bytes(state)

&nbsp; &nbsp;&nbsp;# 逆向初始轮密钥加
&nbsp; &nbsp; state = add_round_key(state, round_keys[0])

&nbsp; &nbsp;&nbsp;return&nbsp;state

4.4 步骤4:实现积分密码分析攻击

# integral_attack.py
from&nbsp;aes&nbsp;import&nbsp;*

def&nbsp;find_key_byte_candidates(ciphertexts, byte_index):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 对指定字节位置找到所有可能的密钥字节候选值

&nbsp; &nbsp; 参数:
&nbsp; &nbsp; &nbsp; &nbsp; ciphertexts: 256个密文列表
&nbsp; &nbsp; &nbsp; &nbsp; byte_index: 要恢复的字节位置(0-15)

&nbsp; &nbsp; 返回:
&nbsp; &nbsp; &nbsp; &nbsp; 候选密钥字节列表
&nbsp; &nbsp; """
&nbsp; &nbsp; candidates = []

&nbsp; &nbsp;&nbsp;for&nbsp;candidate_key_byte&nbsp;in&nbsp;range(256):
&nbsp; &nbsp; &nbsp; &nbsp; xor_sum =&nbsp;0

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;ciph&nbsp;in&nbsp;ciphertexts[:256]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 步骤1: 去除AddRoundKey
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; after_key = ciph[byte_index] ^ candidate_key_byte

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 步骤2: 去除SubBytes
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; after_inv_sub = inv_sbox[after_key]

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 累积异或
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; xor_sum ^= after_inv_sub

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 如果异或和为0,这是一个候选值
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;xor_sum ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; candidates.append(candidate_key_byte)

&nbsp; &nbsp;&nbsp;return&nbsp;candidates

def&nbsp;reverse_key_expansion(last_round_key):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 从第4轮密钥逆向恢复主密钥

&nbsp; &nbsp; 参数:
&nbsp; &nbsp; &nbsp; &nbsp; last_round_key: 第4轮的16字节轮密钥

&nbsp; &nbsp; 返回:
&nbsp; &nbsp; &nbsp; &nbsp; 16字节主密钥
&nbsp; &nbsp; """
&nbsp; &nbsp; Rcon = [0x01,&nbsp;0x02,&nbsp;0x04,&nbsp;0x08,&nbsp;0x10]

&nbsp; &nbsp; w = [None] *&nbsp;20

&nbsp; &nbsp;&nbsp;# 设置第4轮密钥(w[16:20])
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(4):
&nbsp; &nbsp; &nbsp; &nbsp; w[16&nbsp;+ i] = [last_round_key[4*i], last_round_key[4*i+1],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;last_round_key[4*i+2], last_round_key[4*i+3]]

&nbsp; &nbsp;&nbsp;# 逆向推导 w[15] -> w[14] -> ... -> w[0]
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(19,&nbsp;3,&nbsp;-1):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;i %&nbsp;4&nbsp;==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp = w[i-1][:]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp = temp[1:] + temp[:1] &nbsp;# RotWord
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp = [sbox[b]&nbsp;for&nbsp;b&nbsp;in&nbsp;temp] &nbsp;# SubWord
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; temp[0] ^= Rcon[i//4&nbsp;-&nbsp;1]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; w[i-4] = [w[i][j] ^ temp[j]&nbsp;for&nbsp;j&nbsp;in&nbsp;range(4)]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; w[i-4] = [w[i][j] ^ w[i-1][j]&nbsp;for&nbsp;j&nbsp;in&nbsp;range(4)]

&nbsp; &nbsp;&nbsp;# 转换为主密钥(列优先)
&nbsp; &nbsp; master_key = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(4):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;in&nbsp;range(4):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; master_key.append(w[i][j])

&nbsp; &nbsp;&nbsp;return&nbsp;master_key

4.5 步骤5:完整攻击脚本

# attack.py
from&nbsp;extract_ciphertexts&nbsp;import&nbsp;parse_output
from&nbsp;integral_attack&nbsp;import&nbsp;find_key_byte_candidates, reverse_key_expansion
from&nbsp;aes_4rounds&nbsp;import&nbsp;aes_decrypt_4rounds
from&nbsp;itertools&nbsp;import&nbsp;product

print("="&nbsp;*&nbsp;60)
print("Peoples Square 6 - 积分密码分析攻击")
print("="&nbsp;*&nbsp;60)

# 1. 加载密文
print("\n[步骤1] 加载密文数据...")
ciphertexts_0, ciphertexts_1 = parse_output("output.txt")
print(f"成功加载&nbsp;{len(ciphertexts_0)}&nbsp;对密文")

# 2. 对每个字节位置执行积分攻击
print("\n[步骤2] 执行积分密码分析,恢复第4轮密钥候选值...")
all_candidates = []

for&nbsp;i&nbsp;in&nbsp;range(16):
&nbsp; &nbsp; candidates = find_key_byte_candidates(ciphertexts_0, i)
&nbsp; &nbsp; all_candidates.append(candidates)
&nbsp; &nbsp; print(f" &nbsp;字节位置&nbsp;{i:2d}:&nbsp;{len(candidates):3d}&nbsp;个候选 -&nbsp;{candidates}")

# 计算总组合数
total =&nbsp;1
for&nbsp;candidates&nbsp;in&nbsp;all_candidates:
&nbsp; &nbsp; total *= len(candidates)
print(f"\n总候选组合数:&nbsp;{total}")

# 3. 尝试所有候选组合
print("\n[步骤3] 测试所有候选组合,恢复主密钥...")

encrypted_flag = [0xaf,&nbsp;0x93,&nbsp;0xce,&nbsp;0xae,&nbsp;0x1f,&nbsp;0x1e,&nbsp;0x7a,&nbsp;0x13,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x26,&nbsp;0xd6,&nbsp;0x05,&nbsp;0x51,&nbsp;0x97,&nbsp;0x3c,&nbsp;0x46,&nbsp;0x1b,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0xc9,&nbsp;0xb1,&nbsp;0x56,&nbsp;0x9c,&nbsp;0x2c,&nbsp;0xdf,&nbsp;0xd5,&nbsp;0x5a,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0xc6,&nbsp;0xca,&nbsp;0x33,&nbsp;0x46,&nbsp;0x31,&nbsp;0xfb,&nbsp;0x19,&nbsp;0x73]

tested =&nbsp;0
for&nbsp;last_round_key&nbsp;in&nbsp;product(*all_candidates):
&nbsp; &nbsp; tested +=&nbsp;1
&nbsp; &nbsp;&nbsp;if&nbsp;tested %&nbsp;100&nbsp;==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; print(f" &nbsp;已测试&nbsp;{tested}/{total}&nbsp;个组合...")

&nbsp; &nbsp; last_round_key = list(last_round_key)

&nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 恢复主密钥
&nbsp; &nbsp; &nbsp; &nbsp; master_key = reverse_key_expansion(last_round_key)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 尝试解密第一个密文,验证明文格式
&nbsp; &nbsp; &nbsp; &nbsp; dec0 = aes_decrypt_4rounds(ciphertexts_0[0], master_key)
&nbsp; &nbsp; &nbsp; &nbsp; dec1 = aes_decrypt_4rounds(ciphertexts_1[0], master_key)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 验证:plaintext_0应以[00]*8开头,plaintext_1应以[01]*8开头
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;dec0[0:8] == [0]*8&nbsp;and&nbsp;dec1[0:8] == [1]*8:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(f"\n找到正确的主密钥!")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(f"主密钥:&nbsp;{' '.join(f'{b:02x}'&nbsp;for&nbsp;b&nbsp;in&nbsp;master_key)}")

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 解密flag
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag_part1 = aes_decrypt_4rounds(encrypted_flag[:16], master_key)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag_part2 = aes_decrypt_4rounds(encrypted_flag[16:], master_key)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag = flag_part1 + flag_part2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag_str =&nbsp;''.join(chr(b)&nbsp;for&nbsp;b&nbsp;in&nbsp;flag)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(f"\nFlag:&nbsp;{flag_str}")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp;&nbsp;except:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
else:
&nbsp; &nbsp; print("\n未找到匹配的密钥")

print("\n"&nbsp;+&nbsp;"="&nbsp;*&nbsp;60)
print("攻击完成!")
print("="&nbsp;*&nbsp;60)

4.6 运行结果

运行攻击脚本后,输出如下:

============================================================
Peoples Square 6 - 积分密码分析攻击
============================================================

[步骤1] 加载密文数据...
成功加载 1024 对密文

[步骤2] 执行积分密码分析,恢复第4轮密钥候选值...
&nbsp; 字节位置 &nbsp;0: &nbsp; 2 个候选 - [95, 246]
&nbsp; 字节位置 &nbsp;1: &nbsp; 1 个候选 - [246]
&nbsp; 字节位置 &nbsp;2: &nbsp; 2 个候选 - [1, 99]
&nbsp; 字节位置 &nbsp;3: &nbsp; 2 个候选 - [78, 187]
&nbsp; 字节位置 &nbsp;4: &nbsp; 1 个候选 - [123]
&nbsp; 字节位置 &nbsp;5: &nbsp; 1 个候选 - [106]
&nbsp; 字节位置 &nbsp;6: &nbsp; 2 个候选 - [98, 223]
&nbsp; 字节位置 &nbsp;7: &nbsp; 1 个候选 - [96]
&nbsp; 字节位置 &nbsp;8: &nbsp; 1 个候选 - [211]
&nbsp; 字节位置 &nbsp;9: &nbsp; 3 个候选 - [44, 63, 102]
&nbsp; 字节位置 10: &nbsp; 2 个候选 - [192, 234]
&nbsp; 字节位置 11: &nbsp; 1 个候选 - [167]
&nbsp; 字节位置 12: &nbsp; 3 个候选 - [9, 135, 234]
&nbsp; 字节位置 13: &nbsp; 1 个候选 - [36]
&nbsp; 字节位置 14: &nbsp; 2 个候选 - [146, 166]
&nbsp; 字节位置 15: &nbsp; 1 个候选 - [107]

总候选组合数: 576

[步骤3] 测试所有候选组合,恢复主密钥...
&nbsp; 已测试 100/576 个组合...
&nbsp; 已测试 200/576 个组合...
&nbsp; 已测试 300/576 个组合...

找到正确的主密钥!
主密钥: [密钥的16个字节]

Flag: 0CTF{...}

============================================================
攻击完成!
============================================================

第五部分:攻击复杂度分析

5.1 时间复杂度

积分攻击阶段:

  • 对每个字节位置:尝试256个候选值
  • 每个候选值:处理256个密文
  • 每个密文:1次异或 + 1次S-box查表
  • 总计:16 × 256 × 256 ≈ 2^20 次基本操作

密钥验证阶段:

  • 候选组合:通常在几百到几千之间(本题为576)
  • 每个组合:1次密钥逆推 + 2次解密验证
  • 总计:< 10,000 次操作

总复杂度:约2^20,远小于暴力破解的2^128

5.2 数据复杂度

  • 需要256个选择明文-密文对
  • 明文集必须满足积分性质(一个字节取所有可能的值)
  • 本题output.txt恰好提供了所需的数据

5.3 内存需求

  • 存储256个密文:256 × 16 = 4KB
  • 存储S-box和逆S-box:512字节
  • 候选密钥列表:最多16 × 256 = 4KB
  • 总计:< 10KB

5.4 成功率

  • 理论上:100%(假设实现正确)
  • 实际上:可能有少量假阳性候选值(每个字节约1-3个候选)
  • 通过明文格式验证可以排除假阳性

第六部分:安全启示与防御措施

6.1 为什么4轮AES不安全

标准AES-128使用10轮加密,这是经过严格的密码分析和安全评估得出的结果。减少轮数会导致:

  1. 扩散不充分
  • 明文的一个字节变化无法影响到所有密文字节
  • 局部特性可以追踪
  • 4轮后只能保证每个明文字节影响所有密文字节,但统计特性仍可追踪
  1. 混淆不够
  • 积分性质在4轮后仍然可以被追踪
  • 统计特性没有被充分打乱
  • 攻击者可以利用这些特性恢复密钥
  1. 安全边界
  • 积分攻击可以破解最多5轮AES(在某些条件下)
  • 差分攻击、线性攻击等也对低轮数AES有效
  • 10轮提供了足够的安全边界

6.2 密码学实践建议

  1. 使用标准实现
  • 不要自己实现密码算法
  • 使用经过广泛验证的密码库(如OpenSSL、libsodium)
  • 遵循标准规范(NIST FIPS等)
  1. 不要修改参数
  • 不要为了性能而减少轮数
  • 不要修改S-box、轮常数等参数
  • 这些参数经过精心设计,任何修改都可能引入漏洞
  1. 正确使用加密模式
  • AES只是一个分组密码,需要配合加密模式使用(如GCM、CBC等)
  • 不要使用ECB模式(会泄露明文模式)
  • 使用认证加密(AEAD)防止篡改
  1. 密钥管理
  • 使用安全的随机数生成器生成密钥
  • 定期更换密钥
  • 安全存储密钥(使用HSM等)
  1. 侧信道防护
  • 注意时序攻击、功耗分析等侧信道攻击
  • 使用常数时间实现
  • 在关键环境中使用硬件加密

6.3 CTF与实际应用的差异

在CTF中:

  • 可能遇到各种弱化或修改的密码算法
  • 这些都是教学目的,帮助理解密码分析
  • 攻击这些弱化算法有助于理解密码学原理

在实际应用中:

  • 必须使用标准、经过验证的密码算法
  • 安全性是首要考虑,性能其次
  • 遵循安全最佳实践,不要自作聪明

第七部分:总结与进阶学习

7.1 本文总结

通过这道Peoples Square 6题目,我们完整学习了:

  1. AES加密原理
  • 状态矩阵和列优先存储
  • SubBytes、ShiftRows、MixColumns、AddRoundKey四种基本操作
  • 密钥扩展算法
  • 标准AES-128的10轮结构
  1. 积分密码分析
  • 积分集的概念(A和C属性)
  • 积分性质在轮函数中的传播
  • 利用异或和为0的性质恢复密钥
  • 从轮密钥逆推主密钥
  1. 实际攻击实现
  • 从二进制程序逆向出加密逻辑
  • 提取和解析密文数据
  • 完整实现AES组件
  • 编写自动化攻击脚本
  1. 密码学实践
  • 为什么标准参数很重要
  • 密码算法的安全边界
  • 正确使用密码学的原则

7.2 进阶学习方向

密码分析技术:

  • 差分密码分析(Differential Cryptanalysis)
  • 线性密码分析(Linear Cryptanalysis)
  • 中间相遇攻击(Meet-in-the-Middle Attack)
  • 侧信道攻击(Side-Channel Attack)
  • 相关密钥攻击(Related-Key Attack)

其他分组密码:

  • DES/3DES
  • Serpent
  • Twofish
  • ChaCha20

高级主题:

  • 认证加密(Authenticated Encryption)
  • 格密码(Lattice-based Cryptography)
  • 后量子密码学(Post-Quantum Cryptography)
  • 零知识证明(Zero-Knowledge Proofs)

7.3 参考资料

标准文档:

  • NIST FIPS 197: Advanced Encryption Standard (AES)
  • ISO/IEC 18033-3: Block ciphers

学术论文:

  • “Integral Cryptanalysis” by Lars Knudsen
  • “The Block Cipher Square” by Joan Daemen et al.
  • “Improved Integral Attacks on MISTY and KASUMI” by multiple authors

在线课程:

  • OpenSecurityTraining: Cryptanalysis Course
  • Coursera: Cryptography I & II by Dan Boneh
  • MIT OpenCourseWare: Introduction to Cryptography

书籍推荐:

  • “The Design of Rijndael” by Joan Daemen and Vincent Rijmen
  • “Understanding Cryptography” by Christof Paar and Jan Pelzl
  • “Serious Cryptography” by Jean-Philippe Aumasson

结语

Peoples Square 6是一道精心设计的密码学题目,它通过一个弱化的AES实现,让我们有机会动手实践积分密码分析这一重要的密码分析技术。

通过完成这道题目,我们不仅学习了AES的内部机制,更重要的是理解了:

  • 为什么密码算法需要足够的轮数
  • 如何通过统计特性来分析密码系统
  • 密码学中”安全边界”的重要性
  • 正确使用密码学的基本原则

密码学是一门需要极其谨慎的学科。在实际应用中,我们应该始终使用标准的、经过充分验证的密码算法和实现,避免为了性能或其他原因而牺牲安全性。任何看似微小的修改都可能导致灾难性的安全漏洞。

希望本文能够帮助读者深入理解AES和积分密码分析,在密码学的学习道路上更进一步!


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:破镜安全 破镜安全《Peoples Square 6: 4轮AES积分密码分析完整实战》

评论:0   参与:  0