在写这篇博客前,笔者刚赶完密码学与信息安全的实验报告,用了一天把这个问题涉及的算法仔仔细细啃了一遍,忍不住想写一篇博客。为什么呢?因为笔者昨天在查找资料时真的吐槽到无力!!各大博客网站的博文基本就那几篇,搬来搬去也不加自己的理解,甚至代码都没自己调通一遍。。。另外S_box相关问题的博文倒有几篇不错的,可惜基本都是C或者Java写的,终于看到一个Python的了,那代码冗杂得完全就是C直接翻译过来的,重点还是错的。。。最后,声明一下,笔者上课基本睡觉,涉及的知识基本就这两天看的William Stallings的那本密码编码学P86页,所以知识水平不高,以下写的内容,目标都是编出能用且优雅的代码,Heavy Math只说明不解释。
笔者期望好好写篇关于AES乘法逆元的博文,力求讲清楚每个算法,以免后来人爬我爬过的坑,如果你在这篇文章找到了解决方案,点个赞再走呗(づ ̄3 ̄)づ╭❤~
规定GF(2⁸)中的多项式
f ( x ) = a 7 x 7 + a 6 x 6 + ⋯ + a 1 x + a 0 = ∑ i = 0 7 a i x i f(x)=a_{7}x^{7}+a_{6}x^{6}+\cdots+a_1x+a_0=\sum_{i=0}^{7}a_ix^i f(x)=a7x7+a6x6+⋯+a1x+a0=i=0∑7aixi
其系数 a i a_i ai 只能为1或者0,则该多项式可以由组成它的二进制系数 ( a 7 , a 6 , a 5 , a 4 , a 3 , a 2 , a 1 , a 0 ) (a_7,a_6,a_5,a_4,a_3,a_2,a_1,a_0) (a7,a6,a5,a4,a3,a2,a1,a0) 组成。将这些数字按高位到低位排列,则可以形成一个二进制整数,整数的范围为0~256,代表了有限域中的256个多项式。
大部分场景,包括S_box问题中,都遇到一个特殊的多项式 m ( x ) = x 8 + x 4 + x 3 + x + 1 m(x)=x^8+x^4+x^3+x+1 m(x)=x8+x4+x3+x+1 ,称之为不可约多项式。
简单来说,相当于自然数中的质数,也就是它的的因式只有1和它本身,所以称为不可约多项式。
到了这里,笔者开始飞炸,觉得自己15年读了个假数学。。。认真看了会书,笔者才看懂,原来多项式运算分为3种:
所以,我们的乘法逆元要用到的就是第三种,也就是按正常运算完后再对一个 m ( x ) m(x) m(x) 取余数,才是最终运算结果。惊了吧,15年就只学了个普通数学!!!
接下来笔者就举一个例子,各位读者。。爱看不看。。
现在假设这个 m ( x ) m(x) m(x) 就是我们刚才说的不可约多项式 m ( x ) = x 8 + x 4 + x 3 + x + 1 m(x)=x^8+x^4+x^3+x+1 m(x)=x8+x4+x3+x+1,对于多项式 f ( x ) = x 6 + x 4 + x 2 + x + 1 f(x)=x^6+x^4+x^2+x+1 f(x)=x6+x4+x2+x+1 和 g ( x ) = x 7 + z + 1 g(x)=x^7+z+1 g(x)=x7+z+1 ,做它们的加法和乘法。
坐稳了,这车我也不知道往哪开了。。。
惨不忍睹的加法:
f ( x ) + g ( x ) = x 7 + x 6 + x 4 + x 2 f(x)+g(x)=x^7+x^6+x^4+x^2 f(x)+g(x)=x7+x6+x4+x2
匪夷所思的乘法:
f ( x ) × g ( x ) = f ( x ) × g ( x ) m o d m ( x ) = x 7 + x 6 + 1 f(x)\times g(x)=f(x)\times g(x) mod m(x)=x^7+x^6+1 f(x)×g(x)=f(x)×g(x)modm(x)=x7+x6+1
总而言之,在这种运算规则中,做加法时,两边都有的幂次项会消失,只有一边有的幂次项会保留,也就是异或,具体原因请查模运算法则。
至于乘法和除法的就相当复杂了,我们在算法步骤中再仔细说明,要详细解释的话就成数论博文了,当笔者只是个码农啊啊啊。
在有限域中我们来定义:
b ( x ) × b − 1 ( x ) m o d    a ( x ) = 1 b(x)\times b^{-1}(x)\mod a(x)=1 b(x)×b−1(x)moda(x)=1
满足这个条件的 b − 1 ( x ) b^{-1}(x) b−1(x) 则称为 b ( x ) b(x) b(x) 模 a ( x ) a(x) a(x) 的乘法逆元。上述条件等价于下面这个条件:
a ( x ) v ( x ) + b ( x ) w ( x ) = 1 a(x)v(x)+b(x)w(x)=1 a(x)v(x)+b(x)w(x)=1
这里的 w ( x ) w(x) w(x) 就是要求的乘法逆元。所以这就是我们最终要解决的问题啦,解出这个多项式等式就可以了。做过自然数乘法逆元的应该就很熟悉,用扩展欧几里得算法可以解决这个问题。
关于扩展欧几里得算法,笔者不想重复书上的内容去解释这个内容,我们只看算法步骤,因为笔者对欧几里得算法的体验也只是到辗转相除法,再后面变化出来的妖魔鬼怪算法一律很务实地没看了。
我们先做一些符号的约定
符号 | 含义 |
---|---|
a ( x ) a(x) a(x) | 不可约多项式 |
b ( x ) b(x) b(x) | 求乘法逆元的多项式 |
q ( x ) q(x) q(x) | 整除的商多项式 |
r ( x ) r(x) r(x) | 除法产生的余数多项式 |
v ( x ) v(x) v(x) | 系数多项式 |
w ( x ) w(x) w(x) | 所求的乘法逆元 |
接下来用最简单的方式来看这个算法的过程,笔者也是只看了书上那一页的内容写出来的代码:
第一步
r − 1 ( x ) = a ( x ) , v − 1 = 1 , w − 1 = 0 r_{-1}(x)=a(x),v_{-1}=1,w_{-1}=0 r−1(x)=a(x),v−1=1,w−1=0
第二步
r 0 ( x ) = b ( x ) , v 0 = 0 , w 0 = 1 r_{0}(x)=b(x),v_{0}=0,w_{0}=1 r0(x)=b(x),v0=0,w0=1
第三步
r 1 ( x ) = a ( x ) m o d    b ( x ) , q 1 = a ( x ) ∣ b ( x ) , v 1 = v − 1 − q 1 ( x ) v 0 ( x ) , w 1 = w − 1 − q 1 ( x ) w 0 ( x ) r_{1}(x)=a(x)\mod b(x),q_{1}=a(x)|b(x),v_{1}=v_{-1}-q_1(x)v_0(x),w_{1}=w_{-1}-q_1(x)w_0(x) r1(x)=a(x)modb(x),q1=a(x)∣b(x),v1=v−1−q1(x)v0(x),w1=w−1−q1(x)w0(x)
第四步
r 2 ( x ) = a ( x ) m o d    b ( x ) , q 2 = a ( x ) ∣ b ( x ) , v 2 = v 0 − q 2 ( x ) v 1 ( x ) , w 2 = w 0 − q 2 ( x ) w 1 ( x ) r_{2}(x)=a(x)\mod b(x),q_{2}=a(x)|b(x),v_{2}=v_{0}-q_2(x)v_1(x),w_{2}=w_{0}-q_2(x)w_1(x) r2(x)=a(x)modb(x),q2=a(x)∣b(x),v2=v0−q2(x)v1(x),w2=w0−q2(x)w1(x)
第五步
r 3 ( x ) = a ( x ) m o d    b ( x ) , q 3 = a ( x ) ∣ b ( x ) , v 3 = v 1 − q 3 ( x ) v 2 ( x ) , w 3 = w 1 − q 3 ( x ) w 2 ( x ) r_{3}(x)=a(x)\mod b(x),q_{3}=a(x)|b(x),v_{3}=v_{1}-q_3(x)v_2(x),w_{3}=w_{1}-q_3(x)w_2(x) r3(x)=a(x)modb(x),q3=a(x)∣b(x),v3=v1−q3(x)v2(x),w3=w1−q3(x)w2(x)
……
……
……
直到出现 r n ( x ) = 0 r_n(x)=0 rn(x)=0 时,此时得到逆元为 w n − 1 ( n ) w_{n-1}(n) wn−1(n)。
那么有了算法步骤,构造一个递归函数就不难了
def poly_gcd(r1, r2, v1=1, v2=0, w1=0, w2=1):
# 从第三步开始呈现递归规律,所以初值默认为第三步开始时
if r2 == 0 or r2 == 1: return w2
# 做多项式除法 r1 ÷ r2 = q3 …… r3
q3, r3 = ...
# 计算v3 = v1 - q3 * v2
# 计算w3 = w1 - q3 * w2
v3, w3 = ...
# 将几个值按位依次赋值,作为下一次的参数,就可以实现递归了
return poly_gcd(r2, r3, v2, v3, w2, w3)
上面的算法思路与自然数求逆元的思路完全一模一样,而且,我们已经定义了多项式可以和有限域内的二进制整数一一对应起来,这就意味着,唯一的区别只是多项式的乘法和除法与代数的计算规则不太一样,那我们只需要自己定义好多项式乘法和除法,就可以解决问题了!
我们已经知道了,多项式的除法其实就是在二进制的除法的基础上加了一点东西而已,那么仔细观察有理多项式的长除法,将幂次项变为一个个1和0,很快我们就可以找到计算规律。
先回忆一下10进制怎么做的除法,例如 4890 ÷ 16 4890\div 16 4890÷16
第一步,拿48除以16,商值为3,而4890比16多了两个数量级,所以商的数量级为10²(左移两位)。余数为4890-300×16,进入第二次除法。
第二步,拿09除以16,商值为0,被除数数量级比除数大1,商的数量级为10。余数为被除数,进入第三次除法。
第三步,拿90除以16,商值为5,90和16同数量级,商数量级为1。余数90-5*16,进入第四次除法。
第四步,拿10除以16,本位得0,余数为被除数,已经小于除数且是最低位了,计算结束。
第五步,将前面的商加起来3×100+0×10+5×1为最终的商,余数为最后一步的余数。
同样的道理,计算二进制的除法,例如 ( 11101 ) 2 ÷ ( 10 ) 2 (11101)_2\div(10)_2 (11101)2÷(10)2,区别在于二进制只有0和1,这就出现了一些很有趣的现象,比如判断被除数能否除除数的时候只要比较最高非零位数就可以,被除数减去除数乘以商得到余数的时候,因为每一次除法的商始终是商值为1数量级为n,所以商去乘除数的时候就相当于将除数左移n位,然后与被除数做异或运算(即模2加法)。
第一步,11101比10大3个数量级,商为1,数量级为3。余数为11101-10<<3=11101-10000=01101,进入第二次除法。
第二步,01101比10大2个数量级,商为1,数量级为2。余数为01101-10<<2=01101-1000=00101,进入第三次除法。
第三步,00101比10大1个数量级,商为1,数量级为1。余数为00101-10<<1=00101-100=00001,进入第四次除法。
第四步,00001比10小,商为0,余数为1.
第五步,将所有商加起来,1<<3 + 1<<2 + 1<<1 + 0 = 1110。余数为1。
所以结果就是 ( 11101 ) 2 ÷ ( 10 ) 2 = ( 1110 ) 2 ⋯ ( 1 ) 2 (11101)_2\div(10)_2=(1110)_2\cdots(1)_2 (11101)2÷(10)2=(1110)2⋯(1)2 ,还原成多项式形式就是:
( x 4 + x 3 + x 2 + 1 ) ÷ ( x ) = ( x 3 + x 2 + x ) m o d    1 (x_4+x_3+x_2+1)\div(x)=(x^3+x^2+x)\mod1 (x4+x3+x2+1)÷(x)=(x3+x2+x)mod1
不可思议对吧,你我都不知道这是什么鬼,可是按运算规则这就是对的。。。
乘法的思路也一样,是从十进制的方法到二进制的,只是在模2运算也就是做加法的时候遵循了另一套计算规则。举个例子,计算 ( 11011 ) 2 × ( 1110 ) 2 (11011)_2\times(1110)_2 (11011)2×(1110)2 ,乘法比除法简单太多了。
我们可以把这个计算拆开成为:
( 11011 ) 2 × ( 1000 ) 2 + ( 11011 ) 2 × ( 100 ) 2 + ( 11011 ) 2 × ( 10 ) 2 + ( 11011 ) 2 × ( 0 ) 2 (11011)_2\times(1000)_2+(11011)_2\times(100)_2+(11011)_2\times(10)_2+(11011)_2\times(0)_2 (11011)2×(1000)2+(11011)2×(100)2+(11011)2×(10)2+(11011)2×(0)2
而且我们知道对于每一个乘式,都是对左数移位,也就是:
( 11011 ) 2 < < 3 + ( 11011 ) 2 < < 2 + ( 11011 ) 2 < < 1 (11011)_2<<3+(11011)_2<<2+(11011)_2<<1 (11011)2<<3+(11011)2<<2+(11011)2<<1
也就是当第n位非零是,左数便左移相应的n位,最后相加就是最终的结果了:
( 11011 ) 2 × ( 1110 ) 2 = ( 10000010 ) 2 (11011)_2\times(1110)_2=(10000010)_2 (11011)2×(1110)2=(10000010)2
# 求最高幂次数
def Nonzero_MSB(value):
v2str = '{:09b}'.format(value)
for i in range(9):
if int(v2str[i]):
return 9-i
# 模2除法:m为被除数。b为除数,q为商,r为余数
def Mode2_div(fx, gx):
n = Nonzero_MSB(fx)
m = Nonzero_MSB(gx)
if n < m:
return [0, fx]
deg = n - m
fx = fx ^ (gx << deg)
[q, r] = Mode2_div(fx, gx)
return [(1 << deg)|q, r]
# v3 = v1 - q3 * v2
def Calculate(v1, q3, v2):
value = 0
for i in range(32):
if(q3 & (1<<i)):
value = value ^ (v2<<i)
return v1^value
def poly_gcd(r1, r2, v1=1, v2=0, w1=0, w2=1):
if r2==0 or r2==1: return w2
q3, r3 = Mode2_div(r1, r2) # q3(x)=r1(x)|r2(x), r2(x)=r1(x) mod r2(x)
v3 = Calculate(v1, q3, v2) # v3 = v1 - q3 * v2
w3 = Calculate(w1, q3, w2) # w3 = w1 - q3 * w2
return poly_gcd(r2,r3,v2,v3,w2,w3)
def sym2int(sym):
power = [sym[i+2] for i in range(len(sym)) if sym[i] == 'x']
if '+1' in sym: power.append('0')
data = 0
for p in power:
data = data | (1<<int(p))
return data
def int2sym(data):
int2str = '{:09b}'.format(data)
sym = ''
for i in range(9):
if int(int2str[i]) == 1:
if 8-i: sym += '+x^%d'%(8-i)
else: sym += '+1'
return sym[1:]
if __name__ == '__main__':
xstr = input('请输入一个多项式(注意以x为变量,幂次以^表示):')
# xstr = 'x^7+x+1'
print('---- 多项式乘法逆元求解完成 ----')
print('>>你输入的多项式 :', xstr)
print('>>默认既约多项式 :', int2sym(283))
print('>>输出的乘法逆元 :', int2sym(poly_gcd(283, sym2int(xstr))
放一张运行结果图,可以当标准数据来调试:
一直以来认为这一点:不要死嗑数学,理论归理论,工程归工程。记得用Matlab写高斯消元法解线性方程组的时候,书上已经归纳出了一个递归迭代的求求和式了,所以应该想的是如何更优雅地利用这个数学结论去完成这个函数的功能,而不是死磕为什么,那是课堂上该做的(当然,笔者上课都是睡觉度过,所以也当笔者在找个合适的借口吧哈哈哈哈哈)!