详解白盒AES以及C代码实现(以CTF赛题讲解白盒AES)
字数 983 2025-08-22 12:22:15
白盒AES详解及C代码实现
1. AES加密流程概述
标准AES加密流程伪代码如下:
state ← plaintext
AddRoundKey(state, k0)
for r = 1 ... 9
SubBytes(state)
ShiftRows(state)
MixColumns(state)
AddRoundKey(state, kr)
SubBytes(state)
ShiftRows(state)
AddRoundKey(state, k10)
ciphertext ← state
2. 白盒AES实现原理
2.1 流程调整
为了实现基于查表的白盒AES,需要对标准流程进行调整:
- 将初始AddRoundKey(state,k0)放入循环
- 将第9轮的AddRoundKey(state,k9)移出循环
- 利用ShiftRows是线性变换、SubBytes是映射变换的特性,可以调换它们的位置而不影响结果
- 在轮密钥也进行行移位的情况下,AddRoundKey和ShiftRow可以调换位置且结果不变
调整后的伪代码:
state ← plaintext
for r = 1 ... 9
ShiftRows(state)
AddRoundKey(state, k_{r-1})
SubBytes(state)
MixColumns(state)
ShiftRows(state)
AddRoundKey(state, k_9)
SubBytes(state)
AddRoundKey(state, k_{10})
ciphertext ← state
2.2 查表实现
T-Boxes表
将AddRoundKey和SubBytes两个过程合并为一个查表过程:
void getTbox(u32 expandedKey[44], u8 tbox[10][16][256]) {
for(int i = 0; i < 10; i++) {
for(int x = 0; x < 256; x++) {
u8 state[16] = {x};
memset(state, x, 16);
add_round_shiftkey(state, expandedKey + 4*i);
subByte(state);
if(i == 9)
add_round_key(state, expandedKey + 40);
for(int z = 0; z < 16; z++)
tbox[i][z][x] = state[z];
}
}
}
Tyi Tables表
MixColumns操作可以分解为查表实现:
void getTyiTable(u8 table[4][256][4]) {
for(int i = 0; i < 256; i++) {
table[0][i][0] = gmul(i, 0x02);
table[0][i][1] = gmul(i, 0x03);
table[0][i][2] = i;
table[0][i][3] = i;
table[1][i][0] = i;
table[1][i][1] = gmul(i, 0x02);
table[1][i][2] = gmul(i, 0x03);
table[1][i][3] = i;
table[2][i][0] = i;
table[2][i][1] = i;
table[2][i][2] = gmul(i, 0x02);
table[2][i][3] = gmul(i, 0x03);
table[3][i][0] = gmul(i, 0x03);
table[3][i][1] = i;
table[3][i][2] = i;
table[3][i][3] = gmul(i, 0x02);
}
}
Xor Table表
void getXorTable(u8 table[16][16]) {
for(int i = 0; i < 16; i++)
for(int j = 0; j < 16; j++)
table[i][j] = i ^ j;
}
合并Tboxs和Tyi_tables
void getTyiBox(u8 tbox[10][16][256], u32 tyibox[9][16][256]) {
u8 tyitable[4][256][4] = {0};
getTyiTable(tyitable);
for(int r = 0; r < 9; r++)
for(int x = 0; x < 256; x++)
for(int j = 0; j < 4; j++)
for(int i = 0; i < 4; i++) {
u32 v0 = tyitable[0][tbox[r][j*4+i][x]][i];
u32 v1 = tyitable[1][tbox[r][j*4+i][x]][i];
u32 v2 = tyitable[2][tbox[r][j*4+i][x]][i];
u32 v3 = tyitable[3][tbox[r][j*4+i][x]][i];
tyibox[r][j*4+i][x] = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3;
}
}
2.3 完整加密实现
void aes_encrypt_by_table(u8 input[16], u8 key[16]) {
u32 a, b, c, d, aa, bb, cc, dd;
u8 result[16] = {0};
u8 tbox[10][16][256] = {0}, xortable[16][16] = {0};
u32 expandedKey[44] = {0}, tyibox[9][16][256] = {0};
expandKey(key, expandedKey);
getTbox(expandedKey, tbox);
getTyiBox(tbox, tyibox);
getXorTable(xortable);
for(int i = 0; i < 9; i++) {
shiftRows(input);
for(int j = 0; j < 4; j++) {
a = tyibox[i][4*j+0][input[4*j+0]];
b = tyibox[i][4*j+1][input[4*j+1]];
c = tyibox[i][4*j+2][input[4*j+2]];
d = tyibox[i][4*j+3][input[4*j+3]];
aa = xortable[(a >> 28) & 0xf][(b >> 28) & 0xf];
bb = xortable[(c >> 28) & 0xf][(d >> 28) & 0xf];
cc = xortable[(a >> 24) & 0xf][(b >> 24) & 0xf];
dd = xortable[(c >> 24) & 0xf][(d >> 24) & 0xf];
input[4*j+0] = ((aa ^ bb) << 4) | (cc ^ dd);
aa = xortable[(a >> 20) & 0xf][(b >> 20) & 0xf];
bb = xortable[(c >> 20) & 0xf][(d >> 20) & 0xf];
cc = xortable[(a >> 16) & 0xf][(b >> 16) & 0xf];
dd = xortable[(c >> 16) & 0xf][(d >> 16) & 0xf];
input[4*j+1] = ((aa ^ bb) << 4) | (cc ^ dd);
aa = xortable[(a >> 12) & 0xf][(b >> 12) & 0xf];
bb = xortable[(c >> 12) & 0xf][(d >> 12) & 0xf];
cc = xortable[(a >> 8) & 0xf][(b >> 8) & 0xf];
dd = xortable[(c >> 8) & 0xf][(d >> 8) & 0xf];
input[4*j+2] = ((aa ^ bb) << 4) | (cc ^ dd);
aa = xortable[(a >> 4) & 0xf][(b >> 4) & 0xf];
bb = xortable[(c >> 4) & 0xf][(d >> 4) & 0xf];
cc = xortable[a & 0xf][b & 0xf];
dd = xortable[c & 0xf][d & 0xf];
input[4*j+3] = ((aa ^ bb) << 4) | (cc ^ dd);
}
}
shiftRows(input);
for(int j = 0; j < 16; j++) {
input[j] = tbox[9][j][input[j]];
}
}
3. 白盒AES攻击实例
3.1 强网杯dot题目分析
攻击方法:
- 通过更改第十轮输入的一个字节(每次不同位置,16个位置)
- 得到16个密文结果(与正确结果会有四个字节的不同)
- 使用phoenixAES工具推导出第十轮的密钥
关键代码:
import phoenixAES
phoenixAES.crack_file('./tracefile', verbose=0)
# Last round key #N found:
# EA9F6BE2DF5C358495648BEAB9FCFF81
使用aes_keyschedule工具获取完整密钥:
> .\aes_keyschedule.exe EA9F6BE2DF5C358495648BEAB9FCFF81 10
K00: 51574232303233486170707947616D65
K01: BF6B0F928F593CDAEE294CA3A94821C6
K02: EF96BB4160CF879B8EE6CB3827AEEAFE
K03: 0F11008D6FDE8716E1384C2EC696A6D0
K04: 97357039F8EBF72F19D3BB01DF451DD1
K05: E9914EA7117AB98808A90289D7EC1F58
K06: 075124A9162B9D211E829FA8C96E80F0
K07: D89CA74CEB73555D035AAFD195B2A0D
K08: 61797FA0AFCE4AF57FFBE00866A0CA05
K09: 9A0D149335C35E664A38BE6E2C98746B
K10: EA9F6BE2DF5C358495648BEAB9FCFF81
最终获得密钥:QWB2023HappyGame
3.2 m1_read题目分析
题目特点:白盒AES + 异或0x66
攻击步骤:
- 使用frida调用函数获取数据
- 由于数据被异或过,使用phoenixAES之前要先异或回来
- 使用phoenixAES获取最后一轮密钥
- 使用aes_keyschedule获取初始密钥
- 解密数据
关键代码:
# 异或处理
for i in range(len(data)):
data[i] = bytes.fromhex(data[i])
raw_data = [''] * 32
for i in range(32):
for j in range(16):
raw_data[i] += "%02x" % (0x66 ^ data[i][j])
# 使用phoenixAES
phoenixAES.crack_file('tracefile', verbose=0)
# Last round key #N found:
# B4EF5BCB3E92E21123E951CF6F8F188E
# 解密
key = bytes.fromhex("00000000000000000000000000000000")
cipher = AES.new(key, AES.MODE_ECB)
print(cipher.decrypt(c))
4. 关键函数实现
4.1 密钥扩展
void expandKey(u8 key[16], u32 expandedKey[44]) {
static u32 rcon[] = {
0x01000000, 0x02000000, 0x04000000, 0x08000000,
0x10000000, 0x20000000, 0x40000000, 0x80000000,
0x1B000000, 0x36000000
};
for(int i = 0; i < 4; i++) {
expandedKey[i] = (key[4*i] << 24) | (key[4*i+1] << 16) |
(key[4*i+2] << 8) | key[4*i+3];
}
for(int i = 4; i < 44; i++) {
u32 temp = expandedKey[i-1];
if(i % 4 == 0) {
temp = (temp << 8) | (temp >> 24);
temp = SBOX[(temp >> 28) & 0xF][(temp >> 24) & 0xF] << 24 |
SBOX[(temp >> 20) & 0xF][(temp >> 16) & 0xF] << 16 |
SBOX[(temp >> 12) & 0xF][(temp >> 8) & 0xF] << 8 |
SBOX[(temp >> 4) & 0xF][temp & 0xF];
temp ^= rcon[i/4 - 1];
}
expandedKey[i] = expandedKey[i-4] ^ temp;
}
}
4.2 有限域乘法
u8 gmul(u8 ap, u8 bp) {
u8 p = 0, a = ap, b = bp;
while(a != 0 && b != 0) {
if(b & 1 != 0)
p ^= a;
if((a & 0x80) != 0)
a = (a << 1) ^ 0x1b;
else
a <<= 1;
b >>= 1;
}
return p & 0xFF;
}
4.3 字节替换
u8 subByte(u8 byte[16]) {
for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
byte[i*4+j] = SBOX[byte[i*4+j] >> 4][byte[i*4+j] & 0xF];
}
4.4 行移位
void shiftRows(u8 state[16]) {
u8 tmp = state[1];
state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = tmp;
tmp = state[2]; state[2] = state[10]; state[10] = tmp;
tmp = state[6]; state[6] = state[14]; state[14] = tmp;
tmp = state[15];
state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = tmp;
}
5. 总结
白盒AES的核心思想是将AES的各个操作步骤通过预先生成的查表来实现,从而隐藏密钥信息。关键点包括:
- 调整AES标准流程以适应查表实现
- 将AddRoundKey和SubBytes合并为T-Boxes
- 将MixColumns分解为Tyi Tables
- 使用Xor Table优化异或操作
- 攻击方法通常基于差分分析,通过修改输入观察输出变化来恢复密钥
实际应用中,白盒AES的实现需要考虑性能和安全性的平衡,同时要防范各种侧信道攻击和差分分析攻击。