在白盒进犯环境中,密钥变得无所遁形,因而诞生了一系列的魔改算法,其间应用最广、效果最好的莫过于白盒算法,该算法在白盒进犯环境下被提出,能够保证密钥在内存剖析、动态调试进程中不被发现。所以咱们将会用系列文章来阐明标准AES存在的问题,以及白盒化怎么完成,白盒算法又面临什么样的挑战。该文章是榜首篇,首要详解AES算法的完成、密钥问题以及毛病剖析等。内容比较枯燥,首要为后续文章做衬托,读者可选择性阅览感兴趣的章节。

一、AES算法简述

首要简略评论AES算法本身,对AES真实了解的读者能够越过这部分。工作方式以及填充方法并非AES独有的内容,这儿不做评论。AES-128接纳16字节的明文输入,16字节的密钥,输出16字节的密文成果。

咱们key设置为2b7e151628aed2a6abf7158809cf4f3c,明文设置为00112233445566778899aabbccddeeff

input_bytes = 0x00112233445566778899aabbccddeeff
key = 0x2b7e151628aed2a6abf7158809cf4f3c

接下来进入AES的国际,下方是全体流程图:

AES白盒算法详解(一)

首要看全体的流程图,咱们发现,AES的全体图景能够分红左右两块,即明文的处理和密钥的编列。明文的处理主体是一个初始化轮密钥加和十轮运算,在初始化轮密钥加十轮运算中都需求运用密钥编列的成果。密钥编列将16个字节经过运算推演出11组轮密钥,每一组16个字节,称之为K0,K1….K10K_0,K_1….K_{10}

二、密钥编列

下面咱们看一下密钥扩展是怎么算出来的,假定密钥Key为: 2b7e151628aed2a6abf7158809cf4f3c。为了区别密钥和密钥编列后的轮密钥,咱们将此时的密钥叫主密钥。

在AES-128中,密钥扩展后得16*11共176字节,运用时逐十六个字节划分红K0,K1,…K10K_0,K_1,…K_{10}运用,可是在生成时,它是逐四个字节生成的,即44*4。咱们无妨用数组来描述它,即一个包括了44个元素的数组,叫WW

这四十四个元素的生成规矩有三种,如下图所示:

AES白盒算法详解(一)

不同色彩代表了不同规矩。最上方的W0,W1,W2,W3W_0,W_1,W_2,W_3 便是主密钥本身切成四段。

Key=2b7e151628aed2a6abf7158809cf4f3cW0=2b7e1516W1=28aed2a6W2=abf71588W3=09cf4f3cKey = 2b7e151628aed2a6abf7158809cf4f3c\ W_0 = 2b7e1516\ W_1 = 28aed2a6\ W_2 = abf71588\ W_3 = 09cf4f3c

2.1 赤色区域

左侧的赤色部分,W4,W8,W12,….W40W_4,W_8,W_{12},….W_{40}的生成杂乱一点。

Wn=g(Wn−1)xorWn−4W_n = g(W_{n-1})quad xorquad W_{n-4}

xor 是异或运算,比如 W4=g(W3)xorW0W_4 = g(W_3)quad xor quad W_0。g(当时元素前面那个元素) 异或 当时元素头顶上那个元素。

那么要害点便是这个 gg 函数了, gg 函数总共三个进程——行移位、S盒替换、字节异或。咱们以W4W4运算中所需的W3W_3为例。

W3=09cf4f3cW_3 = 09cf4f3c\

首要是行移位,也便是循环左移,规矩固定——将最左面的一个字节挪到右边即可

AES白盒算法详解(一)

第二步是S盒替换,S盒是固定的,S盒替换听着很高级,但操作上很简略——将数值本身作为索引取出S数组中对用的值。

SBox = [
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
]
num = 0x13
result = SBox[num]
### 7d

S盒的背面有十分杂乱的常识,据说S盒的规划上留有后门,所以咱们会推出国密算法等,但好在咱们并不需求去了解。

AES白盒算法详解(一)

终究一个进程更简略,将上一步成果中的最高字节和一个固定常量异或。W4的生成是榜首个,用如下rcon表的榜首个元素0x1。W40即第十次,用终究一个元素0x36.

rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]

AES白盒算法详解(一)

W4=g(W3)xorW1=g(09cf4f3c)xor2b7e1516=8b84eb01xor2b7e1516=a0fafe17W_4 = g(W_3)quad xor quad W_1\=g(09cf4f3c) quad xor quad 2b7e1516\=8b84eb01 quad xor quad 2b7e1516 \= a0fafe17

终究一步的异或我用Python算的

print(hex(0x8b84eb01 ^ 0x2b7e1516))

2.2 橙色区域

上图中蓝色和赤色的部分咱们都讲完了,那么橙色部分呢?相当的简略,和赤色部分相似,去掉g函数即可

Wn=Wn−1xorWn−4W_n = W_{n-1}quad xorquad W_{n-4}

打个比方,W5W_5 = W4W_4 ^ W1W_1 = 0xa0fafe17 ^ 0x28aed2a6 = 0x88542cb1。

至此,密钥编列的全体进程就完毕了。

三、明文运算

现在开始剖析明文运算的进程。首要,调整明文的格式,在AES中,数据以statestate的方式核算、中心存储和传输,中文名即状态

从明文转到state方式很简略,以咱们的明文00112233445566778899aabbccddeeff为例。从上到下,从左到右。千万不要倒置顺序,榜首行不是“00 11 22 33”。除此之外,state中的数,咱们一般用十六进制表明,且不加0x前缀,这样看着比较舒服。除非特意强调是十进制,不然下文均为十六进制。

AES白盒算法详解(一)

3.1 轮密钥加

首要是轮密钥加进程,由于是榜首次轮密钥加进程,所以也叫初始轮密钥加。具体进程很简略,只需求将对应的轮密钥和 statestate 一样从上到下,从左到右排列。两个矩阵逐字节异或,这便是轮密钥加进程。

初始的轮密钥加运用 K0K_02b7e151628aed2a6abf7158809cf4f3c

AES白盒算法详解(一)

接下来便是十轮主运算,看如下的伪代码,咱们能够清楚看到一轮运算中有什么,以及第十轮和前九轮有什么区别。

AES白盒算法详解(一)

初始的明文转 statestate 和终究的 statestate 转明文自不用说,然后是初始轮密钥,运用 K0K_0

前九轮运算中,包括四个进程:字节替换,行移位,列混杂,轮密钥加。

第十轮中,包括三个进程:字节替换,行移位,轮密钥加。相比前九轮缺一个列混杂,其余相同。

十轮运算中的轮密钥加,和初始轮密钥加相比,除了运用的轮密钥不同外,并无不同,别离为K1…..K10K_1…..K_{10}

3.2 字节替换

SubBytesSubBytes 字节替换进程,和密钥编列中的S盒替换完全一致,在此不再赘述。

3.3 行移位

ShiftRowsShiftRows 循环左移,和密钥编列中的循环左移相似,但有差异。密钥编列中, gg 函数中也需循环左移,但其间待处理的数据仅有一行,而明文编列中 statestate 是四行。其循环左移规矩如下:榜首行不循环左移,第二行循环左移1字节,第三行循环左移2字节,第四行循环左移3字节。

AES白盒算法详解(一)

3.4 列混杂

相对杂乱的是列混杂进程,列混杂进程触及两块常识,1是矩阵乘法,2是伽罗瓦域的加法和乘法。

先看榜首块——矩阵乘法。首要演示简略的矩阵相乘,

{1245}∗{7426}={abcd}begin{Bmatrix} 1&2\ 4&5 end{Bmatrix}* begin{Bmatrix} 7&4\ 2&6 end{Bmatrix}=begin{Bmatrix} a&b\ c&d end{Bmatrix}

左面准备相乘的两个矩阵,咱们称它俩为矩阵A和矩阵B,怎么求成果矩阵中的abcdabcd ?规矩如下:第m行第n列的值等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。

a是榜首行榜首列,那么便是A的榜首行和B的榜首列元素乘积之和

a=1∗7+2∗2=11a = 1*7+2*2 = 11

同理可得

b=1∗4+2∗6=16c=4∗7+5∗2=38d=4∗4+5∗6=46b = 1*4+2*6=16\c=4*7+5*2=38\d=4*4+5*6=46

{1245}∗{7426}={11163846}begin{Bmatrix} 1&2\ 4&5 end{Bmatrix}* begin{Bmatrix} 7&4\ 2&6 end{Bmatrix}=begin{Bmatrix} 11&16\ 38&46 end{Bmatrix}

所谓乘积之和,指乘法和加法。

再来看AES列混杂中的矩阵乘法,咱们的 statestate ,左面乘如下所示固定矩阵

{2311123111233112}∗{AEIMBFJNCGKODHLP}begin{Bmatrix} 2&3&1&1\ 1&2&3&1\ 1&1&2&3\ 3&1&1&2 end{Bmatrix}*begin{Bmatrix} A&E&I&M\ B&F&J&N\ C&G&K&O\ D&H&L&P end{Bmatrix}

看起来有些杂乱,小例子中是2*2的矩阵要算4个值,这儿是4*4的矩阵要算16个值。咱们这儿只管榜首列,其他列的核算相似。

{2A+3B+C+D………A+2B+3C+D………A+B+2C+3D………3A+B+C+2D………}begin{Bmatrix} 2A+3B+C+D&…&…&…\ A+2B+3C+D&…&…&…\ A+B+2C+3D&…&…&…\ 3A+B+C+2D&…&…&… end{Bmatrix}

列混杂中的的加法和乘法并不是小例子或日常中的那样,其间的加法指异或运算。2A + 3B + C + D 即 2A ^ 3B ^ C ^ D,这也是AddRoundKeysAddRoundKeys 叫轮密钥加而不是轮密钥异或的原因——加法便是异或。那么其间的乘法呢?乘法杂乱一些,想真实理解的读者能够查找伽罗瓦域内乘法。咱们这儿仅考虑如下三种状况,由于AES-128加密中,列混杂的乘法中,仅触及这三个数。

x∗1=?x∗2=?x∗3=?x*1 = ?\ x*2 = ?\ x*3=?\

结合Python代码能够更清晰,函数名中 mul 是multiply(乘)的缩写。

x * 1,成果为x本身

def mul_by_01(num):
    return num

x * 2,首要切换到二进制方式,最高位为0时,比特串左移1比特,最右边补0即可。假如最高位为1,比特串左移1比特,最右边补0,终究再异或上 0x1B

def mul_by_02(num):
    if num < 0x80:
        res = (num << 1)
    else:
        res = (num << 1) ^ 0x1b
    return res % 0x100

x * 3 = (x * 02) + x,注意”加“是异或

def mul_by_03(num):
    return (mul_by_02(num) ^ num)

四、密钥相关问题

接下来咱们评论关于密钥编列更深的一些问题。经过这些问题来挖掘出白盒算法面临的问题。咱们将密钥本身叫主密钥,编列后的轮密钥。咱们首要评论一个问题:能够从轮密钥逆推主密钥吗?

首要评论AES-128:

K00:2b7e151628aed2a6abf7158809cf4f3c
K01:a0fafe1788542cb123a339392a6c7605
K02:f2c295f27a96b9435935807a7359f67f
K03:3d80477d4716fe3e1e237e446d7a883b
K04:ef44a541a8525b7fb671253bdb0bad00
K05:d4d1c6f87c839d87caf2b8bc11f915bc
K06:6d88a37a110b3efddbf98641ca0093fd
K07:4e54f70e5f5fc9f384a64fb24ea6dc4f
K08:ead27321b58dbad2312bf5607f8d292f
K09:ac7766f319fadc2128d12941575c006e
K10:d014f9a8c9ee2589e13f0cc8b6630ca6

假如获取到的是K0K_0,自不用说。假如获取的是K10K_{10}呢?

4.1 K10剖析

d014f9a8c9ee2589e13f0cc8b6630ca6,首要咱们会到WW数组的视图,看K10K_{10}密钥怎么编列出来的。

AES白盒算法详解(一)

K10是W40,W41,W42,W43W_{40},W_{41},W_{42},W_{43}拼起来的,咱们已知K10K_{10},即已知W40,W41,W42,W43W_{40},W_{41},W_{42},W_{43}。有没有方法求K9K_{9}?假如能够,那么同理能够逆推到K0K_{0},即求得了主密钥。

W40=g(W39)xorW36W41=W40xorW37W42=W41xorW38W43=W42xorW39begin{align*}&W_{40} = g(W_{39})quad xor quad W_{36}\ &W_{41} = W_{40}quad xor quad W_{37}\ &W_{42} = W_{41}quad xor quad W_{38}\ &W_{43} = W_{42}quad xor quad W_{39}end{align*}

依据异或的基本性质,异或效果与比特位上,对应的比特位相同则为0,相异则为1

0xor0=01xor0=10xor1=11xor1=00 quad xor quad 0 = 0\ 1 quad xor quad 0 = 1\ 0 quad xor quad 1 = 1\ 1 quad xor quad 1 = 0

由于相同为0,相异为1,那么一个数和本身异或时,由于每个比特位都相同,所以成果为0。

X+X=0X + X = 0

而当某个数和0异或时,本身为0的比特0^0得0,本身为1的比特位1^0得1,这就导致成果和没异或前一样。

X+0=XX + 0 = X

除此之外,异或并不看谁先谁后,A ^ B 与 B ^ A 明显无区别,即具有交换律。

X+Y=Y+XX + Y = Y + X

矩阵乘法不具有交换律,矩阵成果的第m行第n列的值等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。谁在前谁在后影响了用行仍是用列。

下面做改换,左右和W42W_{42}异或

W43=W42xorW39W42xorW43=W42xorW42xorW39W42xorW43=0xorW39W39=W42xorW43W_{43} = W_{42}quad xor quad W_{39}\ W_{42} quad xor quad W_{43} = W_{42} quad xor quad W_{42}quad xor quad W_{39}\ W_{42}quad xor quad W_{43} = 0 quad xorquad W_{39}\ W_{39} = W_{42} quad xor quad W_{43}

K10K_{10}中触及到的四个式子均能够做改动

W36=g(W39)xorW40W37=W40xorW41W38=W41xorW42W39=W42xorW43begin{align*}&W_{36} = g(W_{39}) quad xor quad W_{40}\ &W_{37} = W_{40} quad xor quad W_{41}\ &W_{38} = W_{41} quad xor quad W_{42}\ &W_{39} = W_{42} quad xor quad W_{43}end{align*}

K10K_{10} = d014f9a8c9ee2589e13f0cc8b6630ca6,切成四块

W40=d014f9a8W41=c9ee2589W42=e13f0cc8W43=b6630ca6W_{40} = d014f9a8\ W_{41} = c9ee2589\ W_{42} = e13f0cc8\ W_{43} = b6630ca6
>>> hex(0xd014f9a8^0xc9ee2589)
'0x19fadc21'
>>> hex(0xc9ee2589^0xe13f0cc8)
'0x28d12941'
>>> hex(0xe13f0cc8^0xb6630ca6)
'0x575c006e'

求出了W37W_{37}W38W_{38}W39W_{39},即 K9K_9 的后多半部分,和真实状况比对后发现一致。

4.2 K09剖析

K09:ac7766f319fadc2128d12941575c006e

那么W36W_{36}呢?再复看一下 gg 函数吧

首要循环左移一字节,575c006e 变成 5c006e57

然后逐字节S盒替换,得4a639f5b

>>> Sbox = (
...     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,
... )
>>> hex(Sbox[0x5c])
'0x4a'
>>> hex(Sbox[0x00])
'0x63'
>>> hex(Sbox[0x6e])
'0x9f'
>>> hex(Sbox[0x57])
'0x5b'

终究是首字节和Rcon中的一个字节异或,这是终究一次改换,即用0x36

Rcon = (0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)
>>> hex(0x4a^0x36)
'0x7c'

g函数的成果即7c639f5b

>>> hex(0x7c639f5b ^ 0xd014f9a8)
'0xac7766f3'

完好的K9: ac7766f319fadc2128d12941575c006e,就被咱们剖析出来了。

依据这套流程,咱们能够持续往上推出K0,取得主密钥。即AES能够依托轮密钥逆推出主密钥。严厉的说,AES-128能够经过一轮轮密钥逆推出主密钥,AES-192需求一轮半,AES-256需求两轮轮密钥。感兴趣的读者能够去处理一下AES-192/256。

五、毛病对密文的影响

经过上面的了解,咱们知道获取轮密钥即可逆推出主密钥,那怎么获取轮密钥呢?

在AES中,数据以 statestate 的方式核算、中心存储和传输。考虑一个问题,假如在某个机遇statestate 中的一个字节产生了过错,变成了另一个字节,这会对密文形成什么影响?

咱们能够经过DBI工具(比如Frida),Debuggger(比如IDA),修正二进制文件本身(SO patch)来完成对 statestate 中一个字节的更改,这能够称为引导、诱发一个过错。

因而差分毛病进犯或差分过错进犯都是DFA适宜的姓名,如图是修正 statestate 中榜首个字节的值。

AES白盒算法详解(一)

先解释一下图示,咱们将未修正 statestate 的履行流叫正常状况,修正了state中一字节的履行流叫毛病状况,咱们调查正常和毛病两种状况下,statestate 中产生差异的字节,即图中的粉色格子。

5.1 开始处产生毛病对密文的影响

首要是初始轮密钥加,过错限于这一个字节

AES白盒算法详解(一)

然后是榜首轮的字节替换,过错限于这一个字节

AES白盒算法详解(一)

然后是榜首轮的循环左移,由于是榜首行,所以没动。

AES白盒算法详解(一)

然后是榜首轮的列混杂进程,再次温习矩阵乘法,成果的第m行第n列的值等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和,因而成果中榜首列的每一个元素都受到矩阵B(即下图左面)榜首列中每个元素的影响。因而,一个字节的过错被分散到了一整列。或者说,正常状况和毛病状况在榜首轮列混杂完毕后,有四个字节的值不同。

AES白盒算法详解(一)

然后是榜首轮的轮密钥加,它只效果用当时字节,不会将差异分散出去。

AES白盒算法详解(一)

能够看到,在一轮循环后,一个字节的毛病,被分散到了四个字节上。咱们持续第二轮。

第二轮的字节替换

AES白盒算法详解(一)

第二轮的循环左移,需求注意到,虽然差异仍是四个字节,但被分散到不同的四列去了。

AES白盒算法详解(一)

第二轮的列混杂,每列存在的差异分散到整列,这导致state的全部字节都与原先有差异。

AES白盒算法详解(一)

咱们发现,AES运算中,仅在第二轮 MixColumnsMixColumns 完毕后,一个字节的差异就现已分散到一切字节上了。

咱们能够更仔细的调查到,假如没有 MixColumnsMixColumns ,那么 statestate 中一个字节的差异,不管循环多少轮,也只会带来一个字节的差异。反过来说,每一次 MixColumnsMixColumns ,都会让一个字节的差异变成四个字节的差异。

假如没有 ShiftRowsShiftRows ,一列中的改动不会影响其余列。那么state中一个字节的差异,只会带来一列中四个字节的差异。

列混杂和循环左移这两个进程,供给了 分散 的效果。

5.2 倒数两个列混杂之间产生毛病对密文的影响

假如,毛病并非产生在最初,并且产生在加密的其他方位呢?

咱们关注于倒数两个 MixColumnsMixColumns 中心,即下图这四个机遇点中恣意一个。

AES白盒算法详解(一)

毛病的效果是随机修正state中某一个字节,那么咱们应当意识到,图中四个机遇点,效果是等价的,这在6.1里就验证过了,只要没经过列混杂,那么过错就只影响一个字节。那么“倒数两个列混杂之间产生毛病对密文的影响”只需求看终究一个列混杂前产生毛病对密文的影响即可。

AES白盒算法详解(一)

由于后面这五进程里,只要一次列混杂,所以会导致成果中四个字节改动。这是很好揣度的,咱们再仔细瞧瞧,有没有其他规矩。

假如毛病字节坐落榜首列,那么其终究一定会改动榜首、第八、第十一、第十四这四个字节。

假如毛病字节在第二列呢

AES白盒算法详解(一)

终究会改动第二、第五、第十二、第十五这四个字节。

第三列

AES白盒算法详解(一)

终究会改动第三、第六、第九、第十六这四个字节

第四列

AES白盒算法详解(一)

终究改动第四、第七、第十、第十三这四个字节

咱们发现,成果中产生改动的四个字节有其规矩,只要四种变法。

  • 假如毛病早于倒数第二个列混杂,那么会影响成果中的十六个字节
  • 假如毛病产生在倒数两个列混杂之间,那么会影响成果中的四个字节,且按照毛病所产生的列,仅存在四种“方式”。
  • 假如毛病晚于终究一个列混杂,那么会影响成果中的一个字节

5.3 由密文状况反推毛病产生地点

根据上面的规矩,咱们意识到,能够依据密文的状况,反推毛病产生的地点。差分毛病进犯中,要求毛病产生在5.2中的机遇,假如是白盒场景下,咱们能够剖析二进制代码,在适宜的方位Hook完成。

六、差分毛病剖析

在上一节中,咱们经过图和方块来表明值的改动,是一种定性的剖析,这一节中,咱们要从数理的视点定量剖析。

6.1 数理剖析

假定终究一次MixColumns前state状态如下,咱们修正榜首个字节,A变X

AES白盒算法详解(一)

首要是列混杂进程,请温习第二节中所讲的常识。

{2A+3B+C+D………A+2B+3C+D………A+B+2C+3D………3A+B+C+2D………}and{2X+3B+C+D………X+2B+3C+D………X+B+2C+3D………3X+B+C+2D………}begin{Bmatrix} 2A+3B+C+D&…&…&…\ A+2B+3C+D&…&…&…\ A+B+2C+3D&…&…&…\ 3A+B+C+2D&…&…&… end{Bmatrix}and begin{Bmatrix} 2X+3B+C+D&…&…&…\ X+2B+3C+D&…&…&…\ X+B+2C+3D&…&…&…\ 3X+B+C+2D&…&…&… end{Bmatrix}

接下来是轮密钥加进程,有些读者可能困惑过,第九轮轮密钥运用K9K_9

{2A+3B+C+D+K9,0………A+2B+3C+D+K9,1………A+B+2C+3D+K9,2………3A+B+C+2D+K9,3………}and{2X+3B+C+D+K9,0………X+2B+3C+D+K9,1………X+B+2C+3D+K9,2………3X+B+C+2D+K9,3………}begin{Bmatrix} 2A+3B+C+D+K_{9,0}&…&…&…\ A+2B+3C+D+K_{9,1}&…&…&…\ A+B+2C+3D+K_{9,2}&…&…&…\ 3A+B+C+2D+K_{9,3}&…&…&… end{Bmatrix}and begin{Bmatrix} 2X+3B+C+D+K_{9,0}&…&…&…\ X+2B+3C+D+K_{9,1}&…&…&…\ X+B+2C+3D+K_{9,2}&…&…&…\ 3X+B+C+2D+K_{9,3}&…&…&… end{Bmatrix}

接下来是字节替换,这个进程咱们用S函数表明。

{S(2A+3B+C+D+K9,0)………S(A+2B+3C+D+K9,1)………S(A+B+2C+3D+K9,2)………S(3A+B+C+2D+K9,3)………}and{S(2X+3B+C+D+K9,0)………S(X+2B+3C+D+K9,1)………S(X+B+2C+3D+K9,2)………S(3X+B+C+2D+K9,3)………}begin{Bmatrix} S(2A+3B+C+D+K_{9,0})&…&…&…\ S(A+2B+3C+D+K_{9,1})&…&…&…\ S(A+B+2C+3D+K_{9,2})&…&…&…\ S(3A+B+C+2D+K_{9,3})&…&…&… end{Bmatrix}and begin{Bmatrix} S(2X+3B+C+D+K_{9,0})&…&…&…\ S(X+2B+3C+D+K_{9,1})&…&…&…\ S(X+B+2C+3D+K_{9,2})&…&…&…\ S(3X+B+C+2D+K_{9,3})&…&…&… end{Bmatrix}

然后是循环左移

正常 statestate

AES白盒算法详解(一)

毛病 statestate

AES白盒算法详解(一)

终究是末轮的轮密钥加,K10K_{10}

正常 statestate

AES白盒算法详解(一)

毛病 statestate

AES白盒算法详解(一)

咱们将正常成果称为CC,毛病成果称为C′C’,十六个字节用下标表明。

C0C1C2C3C4C5C6C7C8C9C10C11C12C13C14C15C0′C1′C2′C3′C4′C5′C6′C7′C8′C9′C10′C11′C12′C13′C14′C15’C_0C_1C_2C_3C_4C_5C_6C_7C_8C_9C_10C_{11}C_{12}C_{13}C_{14}C_{15}\C_0’C_1’C_2’C_3’C_4’C_5’C_6’C_7’C_8’C_9’C_10’C_{11}’C_{12}’C_{13}’C_{14}’C_{15}’

6.2 差异点

接下来评论正常和毛病密文的差异。

C0=S(2A+3B+C+D+K9,0)+K10,0C0′=S(2X+3B+C+D+K9,0)+K10,0C7=S(3A+B+C+2D+K9,3)+K10,7C7′=S(3X+B+C+2D+K9,3)+K10,7C10=S(A+B+2C+3D+K9,2)+K10,10C10’=S(X+B+2C+3D+K9,2)+K10,10C13=S(A+2B+3C+D+K9,1)+K10,13C13’=S(X+2B+3C+D+K9,1)+K10,13C_0 = S(2A+3B+C+D+K_{9,0})+K_{10,0}\ C_0′ = S(2X+3B+C+D+K_{9,0})+K_{10,0}\ C_7 = S(3A+B+C+2D+K_{9,3})+K_{10,7}\ C_7′ = S(3X+B+C+2D+K_{9,3})+K_{10,7}\ C_{10} = S(A+B+2 C+3 D+K_{9,2}) +K_{10,10}\ C_{10}’ = S(X+B+2 C+3 D+K_{9,2}) +K_{10,10}\ C_{13} = S(A+2 B+3 C+D+K_{9,1})+K_{10,13}\ C_{13}’ = S(X+2 B+3 C+D+K_{9,1})+K_{10,13}

咱们先看榜首个字节

C0=S(2A+3B+C+D+K9,0)+K10,0(1)C0′=S(2X+3B+C+D+K9,0)+K10,0(2)C_0 = S(2A+3B+C+D+K_{9,0})+K_{10,0}quad (1)\ C_0′ = S(2X+3B+C+D+K_{9,0})+K_{10,0}quad (2)

左右部分上下相加(再次强调,这儿的“加”是异或运算)

C0+C0′=(S(2A+3B+C+D+K9,0)+K10,0)+(S(2X+3B+C+D+K9,0)+K10,0)C_0+C_0′ = (S(2A+3B+C+D+K_{9,0})+K_{10,0}) + (S(2X+3B+C+D+K_{9,0})+K_{10,0})

右式打开

C0+C0′=S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0)+K10,0+K10,0①C_0+C_0′ = S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0}) + K_{10,0} +K_{10,0}quad ①

化简

C0+C0′=S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0)+K10,0+K10,0=S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0)+0=S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0)C_0+C_0′ = S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0}) + K_{10,0} +K_{10,0}\=S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0}) + 0\=S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0})

两次异或相同的数,等于异或0,再等于没做异或。下面反其道而行之:

S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0)=S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0+0)=S(2A+3B+C+D+K9,0)+S(2X+3B+C+D+K9,0+(2A+2A))=S(2A+3B+C+D+K9,0)+S(2A+3B+C+D+K9,0+2A+2X)S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0})\=S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0}+0)\=S(2A+3B+C+D+K_{9,0}) + S(2X+3B+C+D+K_{9,0}+(2A+2A))\=S(2A+3B+C+D+K_{9,0}) + S(2A+3B+C+D+K_{9,0}+ 2A + 2X)

Y0=2A+3B+C+D+K9,0Y_0 = 2A+3B+C+D+K_{9,0}Z=A+XZ = A + X

C0+C0′=S(Y0)+S(Y0+2Z)(3)C_0 + C_0′ = S(Y_0) + S(Y_0+2Z) quad (3)

6.3 引进差分

这儿咱们要引进差分的概念,AA 是正确的输入值,XX 是毛病带来的输入值,AAXX 异或的成果咱们叫它输入差分。同理,CoC_o 是正确的密文,C0′C_0′是毛病带来的密文,C0C_0C0′C_0′异或的成果叫它输出差分。

不用对这些概念感到惊惧,咱们只是为了更好的指代C0+C0′C_0 + C_0′ 以及 A+XA + X,它们在后面要被屡次运用。

考虑(3)式中的已知量与不知道量,在灰盒进犯模型中,咱们能观测加密的成果,但调查不到state的中心量或毛病导致的改动。因而正确密文和毛病密文都可知,正确输入和毛病输入都不知道。进而,输入差分不知道,输出差分已知。式子左面已知,而右边的Y0Y_0以及输出差分ZZ 不知道。

要害点来了,Y0Y_0ZZ 都是单个字节,即0-255之间的数。可是,是否恣意的ZZ都能够带来左面的成果?

假定差分是0x10,Python代码验证如下

SBox = [
    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(x) ^ S(y)
def getSDiff(x, y):
    return SBox[x] ^ SBox[y]
# 乘2,前面讲过的乘法规矩
def mul_by_02(num):
    if num < 0x80:
        res = (num << 1)
    else:
        res = (num << 1) ^ 0x1b
    return res % 0x100
diff = 0x10
# 运用调集去重
zlist = set({})
# 遍历z
for z in range(0x100):
    # 遍历y0
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == diff:
            zlist.add(z)
print(len(zlist))
# run result:127

能够发现,输出差分为0x10的束缚下,Z的取值空间并非0-255Z规模内全部数字,仅有127个值契合条件。

有没有方法进一步束缚 ZZ 的规模?别忘了咱们才用了一个束缚,还有三个输出差分能够用。

C0=S(2A+3B+C+D+K9,0)+K10,0C0′=S(2X+3B+C+D+K9,0)+K10,0C7=S(3A+B+C+2D+K9,3)+K10,7C7′=S(3X+B+C+2D+K9,3)+K10,7C10=S(A+B+2C+3D+K9,2)+K10,10C10’=S(X+B+2C+3D+K9,2)+K10,10C13=S(A+2B+3C+D+K9,1)+K10,13C13’=S(X+2B+3C+D+K9,1)+K10,13C_0 = S(2A+3B+C+D+K_{9,0})+K_{10,0}\ C_0′ = S(2X+3B+C+D+K_{9,0})+K_{10,0}\ C_7 = S(3A+B+C+2D+K_{9,3})+K_{10,7}\ C_7′ = S(3X+B+C+2D+K_{9,3})+K_{10,7}\ C_{10} = S(A+B+2 C+3 D+K_{9,2}) +K_{10,10}\ C_{10}’ = S(X+B+2 C+3 D+K_{9,2}) +K_{10,10}\ C_{13} = S(A+2 B+3 C+D+K_{9,1})+K_{10,13}\ C_{13}’ = S(X+2 B+3 C+D+K_{9,1})+K_{10,13}

对后面三条做相同的操作

令Y1=3A+B+C+2D+K9,3C7+C7′=S(Y1)+S(Y1+3Z)令Y2=A+B+2C+3D+K9,2C10+C10′=S(Y2)+S(Y2+Z)令Y3=A+2B+3C+D+K9,1C13+C13’=S(Y3)+S(Y3+Z)令Y1 = 3A+B+C+2D+K_{9,3}\ C_7 + C_7′ = S(Y_1) + S(Y_1+3Z)\ 令Y_2 = A+B+2 C+3 D+K_{9,2}\ C_{10} + C_{10}’ = S(Y_2) + S(Y_2 + Z)\ 令Y_3 = A+2 B+3 C+D+K_{9,1}\ C_{13} + C_{13}’ = S(Y_3) + S(Y_3 + Z)

C7+C7′C_7 + C_7′C10+C10′C_{10} + C_{10}’C13+C13′C_{13} + C_{13}’都是已知的,那么和C0+C0′C_0 + C_0′相似,也能够对输入差分ZZ 起到束缚效果。

Y1Y_1Y2Y_2Y3Y_3 都在0-255之间,依样画葫芦。

接下来用第二节中的AES Python代码,对榜首个字节做毛病注入

正常密文

8d f4 e9 aa c5 c7 57 3a 27 d8 d0 55 d6 e4 d6 4b
def encrypt(input_bytes, kList):
    '''
    :param input_bytes: 输入的明文
    :param kList: K0-K10
    :return:
    '''
    plainState = text2matrix(input_bytes)
    # 初始轮密钥加
    state = AddRoundKeys(plainState, kList[0:4])
    for i in range(1, 10):
        state = SubBytes(state)
        state = ShiftRows(state)
        # 第九轮列混杂前将state榜首个字节改为0
        if(i==9):
            state[0][0] = 0
        state = MixColumns(state)
        state = AddRoundKeys(state, kList[4 * i:4 * (i + 1)])
    state = SubBytes(state)
    state = ShiftRows(state)
    state = AddRoundKeys(state, kList[40:44])
    return state

注入毛病后的密文

3c f4 e9 aa c5 c7 57 a5 27 d8 2e 55 d6 36 d6 4b

成果的榜首个字节是0x8d,变成了0x3c

成果的第八个字节是0x3a,变成了0xa5

成果的第十一个字节是0xd0,变成了0x2e

成果的第十四个字节是0xe4,变成了0x36

SBox = [
    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(x) ^ S(y)
def getSDiff(x, y):
    return SBox[x] ^ SBox[y]
def mul_by_01(num):
    return num
def mul_by_02(num):
    if num < 0x80:
        res = (num << 1)
    else:
        res = (num << 1) ^ 0x1b
    return res % 0x100
def mul_by_03(num):
    return (mul_by_02(num) ^ num)
z1list = set({})
z2list = set({})
z3list = set({})
z4list = set({})
zlist = set({})
for z in range(0x100):
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == (0x8d^0x3c):
            z1list.add(z)
    for y1 in range(0x100):
        tmp = getSDiff(y1, mul_by_03(z) ^ y1)
        if tmp == (0x3a ^ 0xa5):
            z2list.add(z)
    for y3 in range(0x100):
        tmp = getSDiff(y3, z ^ y3)
        if tmp == (0xd0 ^ 0x2e):
            z3list.add(z)
    for y4 in range(0x100):
        tmp = getSDiff(y4, z ^ y4)
        if tmp == (0xe4 ^ 0x36):
            z4list.add(z)
print(len(z1list))
print(len(z2list))
print(len(z3list))
print(len(z4list))
# run result
# 127
# 127
# 127
# 127

z1list 到 z4list是别离满意1式到4式的 ZZ 的调集,能够发现别离有127个元素满意要求。需求意识到,虽然输入差分不知道,但都是 AAXX 这个毛病带来的,即每个式子中的输入差分Z是同一个Z,Z必须同时满意这四个式子,即ZZ的规模是这四个调集的交集。Python供给了非常便利的API。

zlist = set.intersection(z1list, z2list, z3list, z4list)
print(len(zlist))
print(zlist)
# result
# 15
# {193, 132, 68, 134, 7, 197, 105, 205, 109, 145, 82, 55, 59, 156, 61}

咱们惊喜的发现,ZZ 的规模被缩小到了15个值,或者说十五个候选 ZZ

6.4 进一步探索

咱们可能会有两个困惑

  • 为什么要去缩小ZZ的规模,乃至是求ZZ具体的值?
  • 怎么进一步缩小ZZ的值。
为什么要去缩小、确认ZZ的规模?

先看榜首个问题,如下式子咱们应该不陌生,它是用来束缚ZZ取值规模的四个式子之一。

C0+C0′=S(Y0)+S(Y0+2Z)(3)C_0 + C_0′ = S(Y_0) + S(Y_0+2Z) quad (3)

那么,当ZZ规模缩小,乃至固定时,是不是同理能够束缚Y0Y_0的取值规模呢?即左面的输出差分已知且固定,ZZ有取值束缚,那么0-255规模内的Y0Y_0是不是都能构建等式呢?我想能够验证一下

Z0Z_0现在只要15个可选项,遍历寻找契合条件的Y0Y_0

y0list = set({})
for z in zlist:
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == (0x8d^0x3c):
            y0list.add(y0)
print(len(y0list))
print(y0list)
# 30
# {128, 1, 3, 131, 8, 137, 140, 141, 16, 145, 154, 178, 181, 186, 60, 64, 70, 71, 78, 215, 220, 223, 227, 104, 238, 244, 246, 249, 126, 255}

咱们惊喜的发现,Y0Y_0的取值规模被束缚到30个潜在值里了。别忘了还有下面这个式子

C0=S(2A+3B+C+D+K9,0)+K10,0C_0 = S(2A+3B+C+D+K_{9,0})+K_{10,0}

C0=S(Y0)+K10,0C_0 = S(Y_0) + K_{10,0}

那么咱们能够将K10,0K_{10,0}也束缚在30个值之中。在上面密钥的相关问题评论中,咱们现已验证,AES-128能够依托一轮的轮密钥逆推出主密钥。因而问题就变成了——假如能尽可能束缚ZZ的取值规模,或许能让K10,0K_{10,0}唯一,同理对其余15个K做相同的事,康复轮密钥,终究逆推主密钥。

怎么进一步束缚ZZ

首要咱们发现,对ZZ具有束缚条件的四个式子都被咱们“用完”了。能不能别的再来一些式子呢?比如从头再注入一个毛病,不是又有四组等式了吗?这是行不通的,不要忘了,从头注入毛病的话,输入差分ZZ现已变了,新的等式不能再对本来的ZZ取值规模产生束缚。

那么无妨换个思路,ZZ是改动的,无法经过屡次从头注入来缩小规模,可是K10,nK_{10,n}不会改动!由于密钥始终是那个密钥。所以能够经过屡次注入来缩小K10,nK_{10,n}的规模。

在刚才的毛病注入中,咱们将K10,0K_{10,0}的提名人缩小到30个值之中。那么考虑一下,假如再注入毛病,新的K10,0K_{10,0}能够依样画葫芦出一个取值规模,真实的K10,0K_{10,0}是哪个?咱们不知道。但它必定在榜首次那30个提名人里,也一定在第二个那30个提名人里边。换言之,它在两个规模的交集里。

首要由 Y0Y_0 核算出对应的K10,0K_{10,0}

C0=S(Y0)+K10,0S(Y0)+C0=S(Y0)+S(Y0)+K10,0S(Y0)+C0=K10,0C_0 = S(Y_0) + K_{10,0}\S(Y_0) + C_0 = S(Y_0) + S(Y_0) + K_{10,0}\S(Y_0) + C_0 = K_{10,0}
k = set({})
for y0 in list(y0list):
    k.add(SBox[y0] ^ 0x8d)
print(k)
# {131, 132, 11, 12, 19, 20, 155, 156, 162, 165, 42, 45, 50, 53, 186, 189, 64, 71, 200, 207, 208, 215, 88, 97, 102, 233, 241, 246, 121, 126}

完好代码如下

SBox = [
    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(x) ^ S(y)
def getSDiff(x, y):
    return SBox[x] ^ SBox[y]
def mul_by_01(num):
    return num
def mul_by_02(num):
    if num < 0x80:
        res = (num << 1)
    else:
        res = (num << 1) ^ 0x1b
    return res % 0x100
def mul_by_03(num):
    return (mul_by_02(num) ^ num)
z1list = set({})
z2list = set({})
z3list = set({})
z4list = set({})
zlist = set({})
for z in range(0x100):
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == (0x8d^0x3c):
            z1list.add(z)
    for y1 in range(0x100):
        tmp = getSDiff(y1, mul_by_03(z) ^ y1)
        if tmp == (0x3a ^ 0xa5):
            z2list.add(z)
    for y3 in range(0x100):
        tmp = getSDiff(y3, z ^ y3)
        if tmp == (0xd0 ^ 0x2e):
            z3list.add(z)
    for y4 in range(0x100):
        tmp = getSDiff(y4, z ^ y4)
        if tmp == (0xe4 ^ 0x36):
            z4list.add(z)
zlist = set.intersection(z1list, z2list, z3list, z4list)
y0list = set({})
for z in zlist:
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == (0x8d^0x3c):
            y0list.add(y0)
print(len(y0list))
print(y0list)
k = set({})
for y0 in list(y0list):
    k.add(SBox[y0] ^ 0x8d)
print("k range")
print(k)

从头注入一个新的毛病,相同在 statestate 的榜首个字节

def encrypt(input_bytes, kList):
    '''
    :param input_bytes: 输入的明文
    :param kList: K0-K10
    :return:
    '''
    plainState = text2matrix(input_bytes)
    # 初始轮密钥加
    state = AddRoundKeys(plainState, kList[0:4])
    for i in range(1, 10):
        state = SubBytes(state)
        state = ShiftRows(state)
        # 第九轮列混杂前将state榜首个字节改为1
        if(i==9):
            state[0][0] = 1
        state = MixColumns(state)
        state = AddRoundKeys(state, kList[4 * i:4 * (i + 1)])
    state = SubBytes(state)
    state = ShiftRows(state)
    state = AddRoundKeys(state, kList[40:44])
    return state

正确密文依然 8d f4 e9 aa c5 c7 57 3a 27 d8 d0 55 d6 e4 d6 4b

毛病密文变成 dc f4 e9 aa c5 c7 57 0a 27 d8 26 55 d6 ad d6 4b

0x8d 0xdc

0x3a 0x0a

0xd0 0x26

0xe4 0xad

同理核算K的规模

SBox = [
    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(x) ^ S(y)
def getSDiff(x, y):
    return SBox[x] ^ SBox[y]
def mul_by_01(num):
    return num
def mul_by_02(num):
    if num < 0x80:
        res = (num << 1)
    else:
        res = (num << 1) ^ 0x1b
    return res % 0x100
def mul_by_03(num):
    return (mul_by_02(num) ^ num)
z1list = set({})
z2list = set({})
z3list = set({})
z4list = set({})
zlist = set({})
for z in range(0x100):
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == (0x8d^0xdc):
            z1list.add(z)
    for y1 in range(0x100):
        tmp = getSDiff(y1, mul_by_03(z) ^ y1)
        if tmp == (0x3a ^ 0x0a):
            z2list.add(z)
    for y3 in range(0x100):
        tmp = getSDiff(y3, z ^ y3)
        if tmp == (0xd0 ^ 0x26):
            z3list.add(z)
    for y4 in range(0x100):
        tmp = getSDiff(y4, z ^ y4)
        if tmp == (0xe4 ^ 0xad):
            z4list.add(z)
zlist = set.intersection(z1list, z2list, z3list, z4list)
y0list = set({})
for z in zlist:
    for y0 in range(0x100):
        tmp = getSDiff(y0, mul_by_02(z) ^ y0)
        if tmp == (0x8d^0xdc):
            y0list.add(y0)
print(len(y0list))
print(y0list)
k = set({})
for y0 in list(y0list):
    k.add(SBox[y0] ^ 0x8d)
print("k range")
print(k)
# {129, 130, 4, 7, 16, 19, 149, 150, 168, 171, 45, 46, 57, 58, 188, 65, 66, 196, 199, 208, 211, 85, 86, 104, 107, 237, 249, 250, 124, 127}

将两个 KK 的候选规模取交集

klist1 = {131, 132, 11, 12, 19, 20, 155, 156, 162, 165, 42, 45, 50, 53, 186, 189, 64, 71, 200, 207, 208, 215, 88, 97, 102, 233, 241, 246, 121, 126}
klist2 = {129, 130, 4, 7, 16, 19, 149, 150, 168, 171, 45, 46, 57, 58, 188, 65, 66, 196, 199, 208, 211, 85, 86, 104, 107, 237, 249, 250, 124, 127}
klist = set.intersection(klist1,klist2)
print(klist)
# result
# {208, 19, 45}

咱们惊喜的发现,K10,0K_{10,0}的候选项只剩三个了,再注入一次毛病。statestate 相同方位改成3。

def encrypt(input_bytes, kList):
    '''
    :param input_bytes: 输入的明文
    :param kList: K0-K10
    :return:
    '''
    plainState = text2matrix(input_bytes)
    # 初始轮密钥加
    state = AddRoundKeys(plainState, kList[0:4])
    for i in range(1, 10):
        state = SubBytes(state)
        state = ShiftRows(state)
        # 第九轮列混杂前将state榜首个字节改为3
        if(i==9):
            state[0][0] = 3
        state = MixColumns(state)
        state = AddRoundKeys(state, kList[4 * i:4 * (i + 1)])
    state = SubBytes(state)
    state = ShiftRows(state)
    state = AddRoundKeys(state, kList[40:44])
    return state

第三个毛病注入得到的K10,0K_{10,0}规模如下

{18, 147, 148, 21, 26, 155, 156, 29, 162, 35, 165, 170, 43, 44, 173, 208, 81, 86, 215, 216, 89, 94, 223, 96, 225, 230, 103, 104, 233, 111}

三次毛病注入得到的K10,0K_{10,0}规模取交集

klist1 = {131, 132, 11, 12, 19, 20, 155, 156, 162, 165, 42, 45, 50, 53, 186, 189, 64, 71, 200, 207, 208, 215, 88, 97, 102, 233, 241, 246, 121, 126}
klist2 = {129, 130, 4, 7, 16, 19, 149, 150, 168, 171, 45, 46, 57, 58, 188, 65, 66, 196, 199, 208, 211, 85, 86, 104, 107, 237, 249, 250, 124, 127}
klist3 = {18, 147, 148, 21, 26, 155, 156, 29, 162, 35, 165, 170, 43, 44, 173, 208, 81, 86, 215, 216, 89, 94, 223, 96, 225, 230, 103, 104, 233, 111}
klist = set.intersection(klist1,klist2,klist3)
print(klist)
# result
# {208}

成果就一个元素,208,即0xd0。这正是咱们的demo中,Key在编列后,K10K_{10}榜首个字节。仅依托上面的三个注入,咱们还能够求出K10,7,K10,10,K10,13K_{10,7},K_{10,10},K_{10,13}。由于对KK束缚的式子里,咱们上面只关注了K10,0K_{10,0}。而当毛病注入产生在第二列、第三列、第四列时,就能够求出别的12个K10K_{10}中的字节,进而取得完好的K10K_{10}。在最好的状况下,只需求八次毛病注入,就能够还原完好的K10K_{10}。不那么完美的状况下,数十次,成百上千次毛病注入也是可接受的,本钱并不高。

总结

这么长的文章总算暂告一断落,密码进犯也是很遥远的事,差分毛病进犯是灰盒进犯中的技能,在白盒加密中咱们能够运用它来剥离密钥,正如最初所说,该篇文章是做衬托的,所以讲的尽量详细一些。之后的文章咱们将剖析白盒算法是怎么完成的,以及针对白盒算法咱们怎样进犯,终究白盒算法的终究优化又有哪些思路。