加密算法AES与伽罗瓦域1-数学基础

一、伽罗瓦域

如果m等于一个素数p的n次幂(n为正整数),即:m = p^n时,才存在一个m阶的域。

n等于1时,叫做素域(Prime Field)
n >= 2时,叫做扩展域(Field Extension)
这两种情况加法单位元都为0,乘法单位元1.

都是循环群
素域的运算为模加,模乘
扩展域的运算为模加、素多项式基础上的多项式运算

1、为何必须是这种形式?

同樣大小的兩個有限域一定是同構的。

当m=p为素数时,才能保证集合中的所有的元素都有乘法逆元(0除外)。即对于域中的任一个元素a,总能在域中找到另外一个元素b,使得a*b mod p 等于1。

m = p^n时,使用多项式运算来保证域的条件

因為域與域之間的函數一定是單射,所以F_p一定是嵌在F_p^r裡的,於是他張開了一個線性空間。
GF(28)GF(2^8)如果是F_2上一個域的話,就可以很簡單看成一個八次元的向量,每個坐標都是0或1。

2、G(2n)G(2^n)

G(2),加减法 xor、 乘法 and
G(2n)G(2^n),加减法 xor 、 乘法 扩展欧几里得算法(利用生成元乘除)

二、扩展域

1、用多项式表达的扩展域

对于GF(28)GF(2^8)来说,我们并不能直接用0-255这256个数字来表示了,取而代之的方法,是使用多项式。GF(28)GF(2^8)中的每一个元素,都用形如:
a7x7+a6x6+a5x5+...+a1x+a0a_7x^7 + a_6x^6 + a_5x^5 + ... + a_1x + a_0
这样的多项式表达。其中,a0a7a_0 - a_7是多项式的系数,它们只能从GF(2)中取值。这样,a0a7a_0 - a_7 就代表了一个字节中的对应bit。并且,这些系数的运算法则,也使用刚才GF(2)中提到的加法和乘法法则。来看个例子:下面这两个,都是GF(28)GF(2^8)中的元素:
A(x)=x7+x5+x3+1A(x) = x^7 + x^5 + x^3 + 1
B(x)=x2+1B(x) = x^2 + 1

2、多项式加法

按照之前的约定,它们分别表示二进制数的10101001和00000101。接下来,为了在GF(28)GF(2^8)中计算A(x) + B(x),我们只要按位依次执行GF(2)中的加法(计算机中的xor)就好了,也就是说它等于:

  1 0 1 0 1 0 0 1
+ 0 0 0 0 0 1 0 1
------------------
  1 0 1 0 1 1 0 0
C

把这个结果对应到多项式,就是x7+x5+x3+x2x^7 + x^5 + x^3 + x^2而这个多项式对应的字节,是0xab。

3、多项式减法等于加法,因为逆元就是自己

无所谓的减法(减法就等于加法),或者负系数。所以,x4x4x^4 - x^4就等于x4+x4x^4 + x^4x3-x^3就是x3x^3

4、多项式乘法

GF(28)GF(2^8)多项式的乘法,就麻烦一些了。为了计算A(x)B(x),我们先按照普通代数的计算方法展开多项式:
A(x)B(x)=(x7+x5+x3+1)(x2+1)A(x)B(x) = (x^7 + x^5 + x^3 + 1)(x^2 + 1)
=x9+x7+x5+x2+x7+x5+x3+1\quad = x^9 + x^7 + x^5 + x^2 + x^7 + x^5 + x^3 + 1
=x9+2x7+2x5+x3+x2+1\quad = x^9 + 2x^7 + 2x^5 + x^3 + x^2 + 1
然后,把这个多项式的系数对2取模:

    x^9 + 2x^7 + 2x^5 + x^3 + x^2 + 1
mod 2
--------------------------------------
    x^9                 x^3 + x^2 + 1
C

这样,就可以得到x9+x3+x2+1x^9 + x^3 + x^2 + 1。但问题是x9x^9已经超过了GF(28)GF(2^8)可以表达的范围,它也对应不到计算机的一个字节中的bit。显然这个结果是不对的,怎么办呢?
其实,思路和刚才在处理GF(2)加法结果的时候是类似的。既然我们要把结果限定在x8x^8以内,那就把多项式对x8x^8取模不就行了。道理是这么个道理,不过这个取模的多项式,可不是随便选的。
AES的设计者Vincent Rijmen 使用了一个经典的不可约多项式:P(x)=x8+x4+x3+x+1P(x) = x^8 + x^4 + x^3 + x + 1
什么是不可约呢?就是说,P(x)P(x)不能表示为GF(28)GF(2^8)域中两个多项式的乘积。这和自然数中的素数是类似的,因此,P(x)P(x)也叫素多项式。
由于P(x)的最高次幂是8,因此对P(x)取模,结果就一定会落在GF(28)GF(2^8)里了,我们来算一下。先算xP(x)xP(x)让P(x)升到9次幂:
xP(x)=x9+x5+x4+x2+xxP(x) = x^9 + x^5 + x^4 + x^2 + x
其次,用x9+x3+x2+1x(Px)x^9 + x^3 + x^2 + 1 - x(Px)

  x^9 +             x^3 + x^2     + 1
- x^9 + x^5 + x^4 +       x^2 + x
--------------------------------------
        x^5 + x^4 + x^3       + x + 1
C

这样,得到的结果就落在GF(28)GF(2^8)里了,它表示的二进制数是00111011,这就是GF(28)GF(2^8)A(x)B(x)A(x)B(x)的运算结果。
这个过程,数学上使用多项式长除法,形如:
aes0.png
aes00.jpg
简化计算的方法,避免长除法,如下,因为:
x8 (mod P(x))=P(x)x8=x4+x3+x2+x1 x^8\ (mod\ P(x)) = P(x) -x^8 = x^4+ x^3 +x^2 + x1
将高次多项式的x8x^8替换为x4+x3+x2+1x^4 + x^3 + x^2 +1而不用进行除法取模计算
虽然可以通过替换减少除法计算,但还是过于复杂。因此通过生成元查表来计算。

int table[256];
int i;

table[0] = 1;//g^0
for(i = 1; i < 255; ++i)//生成元为x + 1
{
    //下面是m_table[i] = m_table[i-1] * (x + 1)的简写形式
    table[i] = (table[i-1] << 1 ) ^ table[i-1];
 
    //最高指数已经到了8,需要模上m(x)
    if( table[i] & 0x100 )
    {
        table[i] ^= 0x11B;//用到了前面说到的乘法技巧-替换
    }
}
C

详见下节

5、求多项式的逆元(用于计算乘除法)

在域GF(2^w)中2总是生成元。

了解了多项式乘法之后,我们就能计算GF(28)GF(2^8)中任意一个多项式A(x)的逆元了,AES加密算法需要这个值。要说明的是,选取的素多项式不同,GF(28)GF(2^8)中元素的逆元也不同。这里,我们就用刚才说到的P(x)举例了。
对于给定的一个GF(28)GF(2^8)中的多项式A(x),如果A(x)B(x)的结果对P(x)取模等于1,那么B(x)就是A(x)的逆元了。那该怎么计算这个B(x)呢?
第一种方法,当然就是暴力穷举。用GF(28)GF(2^8)中的每一个元素分别和A(x)计算;
第二种方法,来自于一个有趣的现象。GF(28)GF(2^8)中的每一个非0多项式,都可以表示成(x+1)的n次幂,这里0<=n<=254,例如:
(x+1)0=1(x+1)^0 = 1
(x+1)1=x+1(x+1)^1 = x+1
(x+1)2=x2+1(x+1)^2 = x^2+1
...
(x+1)254=x7+x6+x5+x4+x3+x2+x(x+1)^{254} = x^7+x^6+x^5+x^4+x^3+x^2+x
注意这里我们执行的是GF(2)中的加法和乘法。并且,当n从255开始,就又会用和之前同样的顺序遍历GF(28)GF(2^8)中的多项式。
(x+1)255=1(x+1)^{255} = 1
(x+1)256=x+1(x+1)^{256} = x+1
...
正是由于这种特性,(x+1)有了一个形象的名字,叫做生成元。有了生成元之后,我们可以把GF(28)GF(2^8)中的每一个多项式和它对应的生成元的幂保存成一个映射表。接下来,对于给定的A(x),如果B(x)是A(x)的逆元,则一定有下面的关系成立:
A(x)B(x)=(x+1)a(x+1)b=(x+1)(a+b)=1(mod P(x))A(x)B(x) = (x+1)^a (x+1)^b = (x+1)^(a+b) = 1 (mod\ P(x))
于是,我们只要根据之前创建的映射表,查到A(x)对应的幂a,然后用255-a计算出b,再从表中查出b对应的多项式,就是A(x)的逆元了。

6、利用生成元,查表计算乘除法

乘除法同理,只要查表获取指数,指数相加减mod255后,反查的原数即可。
常用本原多项式 :
GF(23): x3+x+1GF(2^3) : \ x^3+x+1
GF(24): x4+x+1GF(2^4) : \ x^4+x+1
GF(28): x8+x4+x3+x2+1GF(2^8) : \ x^8+x^4+x^3+x^2+1
GF(216): x16+x12+x3+x+1GF(2^{16}) : \ x^{16}+x^{12}+x^3+x+1
GF(232): x32+x22+x2+x+1GF(2^{32}) : \ x^{32}+x^{22}+x^2+x+1
GF(264): x64+x4+x3+x+1GF(2^{64}) : \ x^{64}+x^4+x^3+x+1

通过本原多项式生成元素
设P(x)是GF(2w)GF(2^w)上的某一个本原多项式,GF(2w)GF(2^w)的元素产生步骤是:
1、给定一个初始集合,包含0,1和元素x,即 {0,1,x};
2、将这个集合中的最后一个元素,即x,乘以x,如果结果的度大于等于w,则将结果mod P(x)后加入集合;
3、直到集合有2^w个元素,此时最后一个元素乘以x再mod P(x)的值等于1。

例如,GF(2^4)含有16个元素,本原多项式为P(x)=x^4+x+1,除了 0、1外,另外14个符号均由本原多项式生成。
注意到x^14=x^3+1,此时计算x^15=x^14x=(x^3+1)x=x^4+x=1,生成结束。

生成元素 多项式表示 二进制表示 数值表示 推导过程
0 0 0000 0  
x^0 x^0 0001 1  
x^1 x^1 0010 2  
x^2 x^2 0100 4  
x^3 x^3 1000 8  
x^4 x+1 0011 3 x^3*x = x^4 mod P(x) = x+1
x^5 x^2+x 0110 6 x^4x = (x+1)x = x^2+x
x^6 x^3+x^2 1100 12  
x^7 x^3+x+1 1011 11 x^6x = (x^3+x^2)x = x^4 +x^3 mod P(x) = x^3+x+1
x^8 x^2+1 0101 5  
x^9 x^3+x 1010 10  
x^10 x^2+x+1 0111 7 x^9x=(x^3+x)x = x^4+x^2 mod P(x) = x^2+x+1
x^11 x^3+x^2+x 1110 14  
x^12 x^3+x^2+x+1 1111 15 x^11x=(x^3+x^2+x)x = x^4+x^3+x^2 mod P(x) = x^3+x^2+x+1
x^13 x^3+x^2+1 1101 13 x^12x=(x^3+x^2+1  )x = x^4+x^3+x mod P(x)= x^3+1
x^14 x^3+1 1001 9 x^13x=(x^3+x^2+1)x = x^4+x^3+x mod P(x) = x^3+1
x^15 1 0001 1 x^14x = (x^3+1)x = x^4+x mod P(x) = 1

注意:多项式0 ,是无法用生成元生成的。g0g^0等于多项式1,而不是 0。
根据上文的GF(24)GF(2^4)的元素表示,生成gflog表和gfilog表如下:
aes22.png
查表进行乘法和除法运算的例子
GF(24)GF(2^4)域上的乘法和除法,已知2w1=241=152^w-1 = 2^4 -1 = 15
乘法:

7 * 9 = gfilog[gflog[7] + gflog[9]] = gfilog[10 + 14]
   = gfilog[24 mod 15] = gfilog[9] = 10

除法:

13 / 11 = gfilog[gflog[13] - gflog[11]] =  gfilog[13 - 7] = gfilog[6] = 12

生成gflog,gfilog的python 代码

# coding=UTF-8
# key : value => w : primitive_polynomial
primitive_polynomial_dict = {4: 0b10011,                            # x**4  + x     + 1
                             8: (1 << 8) + 0b11101,                 # x**8  + x**4  + x**3 + x**2 +1
                             16: (1 << 16) + (1 << 12) + 0b1011,    # x**16 + x**12 + x**3 + x    + 1
                             32: (1 << 32) + (1 << 22) + 0b111,     # x**32 + x**22 + x**2 + x    + 1
                             64: (1 << 64) + 0b11011                # x**64 + x**4  + x**3 + x    + 1
                             }

def make_gf_dict(w):
    gf_element_total_number = 1 << w
    primitive_polynomial = primitive_polynomial_dict[w]

    gfilog = [1]  # g(0) = 1
    for i in xrange(1, gf_element_total_number - 1):
        temp = gfilog[i - 1] << 1  # g(i) = g(i-1) * g
        if temp & gf_element_total_number:  # if overflow, then mod primitive polynomial
            temp ^= primitive_polynomial  # mod primitive_polynomial in GF(2**w) == XOR
        gfilog.append(temp)
 
    assert (gfilog[gf_element_total_number - 2] << 1) ^ primitive_polynomial
    gfilog.append(None)
 
    gflog = [None] * gf_element_total_number
    for i in xrange(0, gf_element_total_number - 1):
        gflog[gfilog[i]] = i
 
    print "{:>8}\t{:>8}\t{:>8}".format("i", "gfilog[i]", "gflog[i]")
    for i in xrange(0, gf_element_total_number):
        print "{:>8}\t{:>8}\t{:>8}".format(i, gfilog[i], gflog[i])

if __name__ == "__main__":
   make_gf_dict(4)
Python

来源参考:
https://boxueio.com/series/let-us-build-an-apn-provider/ebook/562
https://blog.csdn.net/shelldon/article/details/54729687

发表新评论