有限域GF(2^8).md

原文:https://blog.csdn.net/luotuo44/article/details/41645597

现在重点讲一下GF(2n),特别是GF(28),因为8刚好是一个字节的比特数。

前面说到, G F ( p ) GF(p) GF(p),p得是一个素数,才能保证集合中的所有元素都有加法和乘法逆元(0除外)。但我们却很希望0到255这256个数字也能组成一个域。因为很多领域需要用到。mod 256的余数范围就是0到255,但256不是素数。小于256的最大素数为251,所以很多人就直接把大于等于251的数截断为250。在图像处理中,经常会这样做。但如果要求图像无损的话,就不能截断。

貌似已经到了死胡同,救星还是有的,那就是 G F ( p n ) GF(p^n) GF(pn),其中p为素数。在这里我们只需令p为2,n为8,即 G F ( 2 8 ) GF(2^8) GF(28)

多项式运算:

G F ( 2 8 ) GF(2^8) GF(28)由一组从 0x000xff 的256个值组成,加上加法和乘法,因此是( 2 8 2^8 28)。GF代表伽罗瓦域,以发明这一理论的数学家的名字命名。 G F ( 2 8 ) GF(2^8) GF(28) 的一个特性是一个加法或乘法的操作的结果必须是在{0x00 ... 0xff}这组数中。虽然域论是相当深奥的,但 G F ( 2 8 ) GF(2^8) GF(28)加法的最终结果却很简单。 G F ( 2 8 ) GF(2^8) GF(28) 加法就是异或(XOR)操作。

要弄懂 G F ( 2 n ) GF(2^n) GF(2n),要先明白多项式运算。这里的多项式和初中学的多项式运算有一些区别。虽然它们的表示形式都是这样的: 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。下面是它的一些特点。

  • 多项式的系数只能是0或者1。当然对于GF(p^n),如果p等于3,那么系数是可以取:0, 1, 2的
  • 合并同类项时,系数们进行异或操作,不是平常的加法操作。比如 x 4 + x 4 x^4 + x^4 x4+x4等于 0 ∗ x 4 0*x^4 0x4。因为两个系数都为1, 进行异或后等于0
  • 无所谓的减法(减法就等于加法),或者负系数。所以,x^4 – x4就等于x4 + x4。-x3就是x^3。

看一些例子吧。对于 f ( x ) = x 6 + x 4 + x 2 + x + 1 。 g ( x ) = x 7 + x + 1 f(x) = x^6 + x^4 + x^2 + x + 1。g(x) = x^7 + x + 1 f(x)=x6+x4+x2+x+1g(x)=x7+x+1
那么:
f ( x ) + g ( x ) = x 7 + x 6 + x 4 + x 2 + ( 1 + 1 ) x + ( 1 + 1 ) 1 = x 7 + x 6 + x 4 + x 2 f(x) + g(x) = x^7 + x^6 + x^4+ x^2 + (1+1)x + (1+1)1 = x^7 + x^6 + x^4 + x^2 f(x)+g(x)=x7+x6+x4+x2+(1+1)x+(1+1)1=x7+x6+x4+x2

f ( x ) – g ( x ) f(x) – g(x) f(x)g(x) 等于 f ( x ) + g ( x ) f(x) + g(x) f(x)+g(x)

多项式乘法:

f ( x ) ∗ g ( x ) = ( x 1 3 + x 1 1 + x 9 + x 8 + x 7 ) + ( x 7 + x 5 + x 3 + x 2 + x ) + ( x 6 + x 4 + x 2 + x + 1 ) = x 1 3 + x 1 1 + x 9 + x 8 + x 6 + x 5 + x 4 + x 3 + 1 f(x) * g(x) =(x^13 + x^11 + x^9 + x^8 + x^7) + (x^7 + x^5 + x^3 + x^2 + x) + (x^6+ x^4 + x^2 + x + 1) = x^13 + x^11 + x^9 + x^8 + x^6 + x^5+ x^4+ x^3+1 f(x)g(x)=(x13+x11+x9+x8+x7)+(x7+x5+x3+x2+x)+(x6+x4+x2+x+1)=x13+x11+x9+x8+x6+x5+x4+x3+1

多项式除法:

得其余数,也就是mod操作的结果。

素多项式:

对于多项式也类似素数,有素多项式。其定义和素数类似,素多项式不能表示为其他两个多项式相乘的乘积。

素多项式模运算:

指数小于3的多项式有8个,分别是 0 0 0 1 1 1 x x x,$ x+1 , , x^2$, x 2 + 1 x^2+1 x2+1 x 2 + x x^2 + x x2+x x 2 + x + 1 x^2+x+1 x2+x+1。对于 G F ( 2 3 ) GF(2^3) GF(23)来说,其中一个素多项式为 x 3 + x + 1 x^3+x+1 x3+x+1。上面8个多项式进行四则运算后 m o d ( x 3 + x + 1 ) mod (x^3+x+1) mod(x3+x+1)的结果都是 8 个之中的某一个(域内值互相四则运算)。当然也可以证明这是一个域,所以每一个多项式都是有加法和乘法逆元的(0除外)。注意,这些逆元都是和素多项式相关的,同一个多项式,取不同的素多项式,就有不同的逆元多项式。

对于 G F ( 2 8 ) GF(2^8) GF(28),其中一个素多项式为 x 8 + x 4 + x 3 + x + 1 x^8 + x^4 + x^3 +x +1 x8+x4+x3+x+1。对应地,小于8次的多项式有256个。

由素多项式得到的域,其加法单位元都是0,乘法单位元是1。

重点来了:

前面讲到了对素多项式取模,然后可以得到一个域。但这和最初的目的有什么关系吗?多项式和0, 1, ……,255没有什么关系。确实是没有什么关系,但多项式的系数确可以组成0, 1, 2,……255这些数。回到刚才的 G F ( 2 3 ) GF(2^3) GF(23),对应的8个多项式,其系数刚好就是 000,001, 010, 011, 100, 101, 110, 111。这不正是0到7这8个数的二进制形式吗?也就是说,它们有一一对应映射的关系。多项式对应一个值,我们可以称这个值为多项式值。
对于 G F ( 2 3 ) GF(2^3) GF(23),取素多项式为 x 3 + x + 1 x^3 + x+1 x3+x+1 ,那么多项式 x 2 + x x^2+x x2+x 的乘法逆元就是x+1。系数对应的二进制分别为110和011。此时,我们就认为对应的十进制数6和3互为逆元。即使 mod 8不能构成一个域,但通过上面的对应映射,0到7这8个数一样有对应逆元了(为了顺口,说成0到7。实际0是没有乘法逆元的)。同样,对于 G F ( 2 8 ) GF(2^8) GF(28) 也是一样的。所以0到255,这256个数都可以通过这样的方式得到乘法逆元(同样,0是没有乘法逆元的)。

G F ( 2 8 ) GF(2^8) GF(28)的四则运算:

其实,通过前面的讲解,已经可以对GF(2^8)进行四则运算了。但计算起来相当麻烦。接下来就是讲解一下怎么用简单的方法进行四则运算,以及编程的实现(对于码农来说,这才是终极目标啊)。

**下面讲解的所有运算,默认的素多项式为 $ x^8 +x^4 + x^3 +x +1 , 用 m ( x ) 表 示 。 ∗ ∗ ,用 m(x) 表示。** ,m(x)GF(2^8)$的素多项式有多个,但这个经典啊。

加法和减法:

加法和减法就是经典的异或运算,没有什么可说的。

乘法:

前面的一个多项式相乘例子有说到怎么进行相乘计算,但过于复杂。《密码编码学与网络安全》一书说到了一个计算乘法的技巧。
首先有, x 8 m o d m ( x ) = [ m ( x ) – x 8 ] = x 4 + x 3 + x + 1 x^8 mod m(x) = [m(x) – x^8] = x^4 + x^3 +x +1 x8modm(x)=[m(x)x8]=x4+x3+x+1

对于多项式f(x), 有:
f ( x ) = b 7 x 7 + b 6 x 6 + b 5 x 5 + b 4 x 4 + b 3 x 3 + b 2 x 2 + b 1 x + b 0 f(x) = b_7x^7 + b_6x^6 + b_5x^5 + b_4x^4 + b_3x^3 + b_2x^2 + b_1x + b_0 f(x)=b7x7+b6x6+b5x5+b4x4+b3x3+b2x2+b1x+b0
x ∗ f ( x ) = ( b 7 x 8 + b 6 x 7 + b 5 x 6 + b 4 x 5 + b 3 x 4 + b 2 x 3 + b 1 x 2 + b 0 x ) m o d m ( x ) x * f(x) = (b_7x^8 + b_6x^7 + b_5x^6 + b_4x^5 + b_3x^4 + b_2x^3 + b_1x ^2+ b_0x) mod m(x) xf(x)=(b7x8+b6x7+b5x6+b4x5+b3x4+b2x3+b1x2+b0x)modm(x)
如果 b 7 b_7 b7等于0,那么结果是一个小于8的多项式,不需要取模计算了。如果 b 7 b_7 b7等于1,那么通过上面所得就有:
$x * f(x) = ( b_6x^7 + b_5x^6 + b_4x^5 + b_3x^4 + b_2x^3 + b_1x ^2+ b_0x) + (x^4 + x^3 +x + 1) $ [1]
对于C语言来说,通过位移运算符<< 和 异或运算,很容易计算。对于x的指数高于一次的情况,可以通过递归的形式使用。如:x^2 * f(x) = x*[x*f(x)]。

虽然有上面的技巧,但还是过于复杂。在大量运算中(比如图像处理),耗时太多。于是人们就想到了通过查表的形式计算。

要弄懂查表的原理,得明白一个概念:生成元g

首先,在群中定义幂运算为重复运用群的运算符。假如运算符为普通的加法,那么幂运算就是多个加法一起使用。

如果元素g满足下面的条件,我们就称g为生成元:对于集合中的任何的一个元素,都可以通过元素g的幂 g k g^k gk得到。并定义 g 0 = e g^0 = e g0=e,假设h为 g 的逆元,那么还定义 g ( − k ) = h k g^{(-k)} = h^k g(k)=hk。比如,整数集合,都可以由生成元1得到。 2 = 1 + 1 = 1 2 2 = 1 + 1 = 1^2 2=1+1=12 3 = 1 3 = 1 + 1 + 1 3 = 1^3=1 + 1 + 1 3=13=1+1+1、……。负数可以通过幂取负数得到

将生成元应用到多项式中, GF(2^n)中的所有多项式都是可以通过多项式生成元g通过幂求得。即域中的任意元素a,都存在一个k,使得a = g^k。

下面看一下,怎么将生成元应用到多项式乘法中。

对于 g k = a g^k = a gk=a,有正过程和逆过程。知道 k 求 a 是正过程,知道了 a 反过来求k是逆过程。同样,假设有 g n = a g^n = a gn=a g m = b g^m = b gm=b 。现在需要求 a ∗ b a*b ab,那么就有 a ∗ b = g n ∗ g m = g ( n + m ) a*b = g^n* g^m = g^(n+m) ab=gngm=g(n+m) 。我们只需要:根据 a 和 b ,分别求得 n 和 m 。然后直接计算 g ( n + m ) g^(n+m) g(n+m) 即可。求,并不是真的傻乎乎地通过计算而得到,而是通过查表。这里,构造两个表,正表和反表。正表是知道了指数,求值。反表是知道了值,求指数。接下来要做的就是构造这两个表。为了做除法运算,还要构造逆元表。
在给出三个表的构造代码前,有几个东西要讲一下。

虽然生成元g的幂次厉害,但多项式0,是无法用生成元生成的。g0等于多项式1,而不是0。为什么?逆向思考一下:假如存在k使得gk = 0,那么g^(k+1)等于多少呢?

G F ( 2 n ) GF(2^n) GF(2n)是一个有限域,就是元素个数是有限的,但指数k是可以无穷的。所以必然存在循环。这个循环的周期是2n-1,因为多项式0,g不能生成,少了一个。所以对于GF(28),当k大于等于255时,g^k =g^(k%255)。所以对于正表,生成元的指数,取0到254即可,对应地生成255个不同的多项式,多项式的取值范围为1到255

有了上面的讨论,对于正表,只需依次计算 g 0 g^0 g0 g 1 g^1 g1 g 2 g^2 g2,……, g 254 g^{254} g254 即可。对于 G F ( 2 8 ) GF(2^8) GF(28),素多项式 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 ,对应的生成元 g ( x ) = x + 1 g(x) = x + 1 g(x)=x+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] 大于 2^8 就 执行上面 [1] 的乘法技巧 
 {
  table[i] ^= 0x11B;//用到了前面说到的乘法技巧  0x11B 就是 2^8 + 2^4 + 2^3 + 1 
 }
}

输出:

正表(十进制)
135151751852552646114150161248
95225567221611514916424726103034
2295292228558923538106190217112144171
8324541220606820479209104184211110
7621210316922459772159816624182440
13115818520810718922012712915217920673219
18119687249164880240112939105187214
25425431251351461732364711314717423332
25122587821010918319493231508625021
195942266171201641929123744116156191
15918621310017223942126130157188223122142
15518219388232351011752343711117720067
252313399165244792745119153176203
6920774222121139134145168227626619881
18549023841123141140143138133148167242
5775221124132151162253283610818019982

这个正表,下标值等于生成元的指数,下标对应的元素值等于对应的多项式值。
例如:下标为 3 的(下标从 0 开始),这里 合并同类项时,系数们进行异或操作,不是平常的加法操作。看上面多项式运算第2条;
( x + 1 ) 3 = ( x + 1 ) 2 ∗ ( x + 1 ) = ( x 2 + x + x + 1 ) ∗ ( x + 1 ) = ( x 2 + 1 ) ∗ ( x + 1 ) = x 3 + x 2 + x + 1 (x + 1)^3 = (x + 1)^2 * (x + 1) = (x^2 + x + x + 1) * (x + 1) = (x^2 + 1) * (x + 1) = x^3 + x^2 + x + 1 (x+1)3=(x+1)2(x+1)=(x2+x+x+1)(x+1)=(x2+1)(x+1)=x3+x2+x+1
那么 该值就是 2 3 + 2 2 + 2 + 1 = 15 2^3 + 2^2 + 2 + 1 = 15 23+22+2+1=15

反表和正表是对应的,所以反表中元素的个数也是255个。正表中,生成元g的指数k的取值范围为0到254。多项式值 g k g^k gk 的取值范围为1到255。所以在反表中,下标的取值范围为1到255,元素值的取值范围为0到254。实现代码如下:

int arc_table[256];
 
for(i = 0; i < 255; ++i)
 arc_table[ table[i] ] = i;
反表 (十进制)
***025150226198751992710451238
10042241452141129239761138200248105
125194291812491853910677228166114154201
101471385331522536182401306953147
1501432191895420820614819922102416470
102221253481916139981793722615234136
126110721951631823066581074084250133
4312110211551599420278212172229243115
175881688024423421411679174233213231230
4421511712223522112458920395176156169
127122461112319673236216673145164118
204187629025196177134598216110817085
151178135144971902202521881492072055563
835713260651621097120421589386242
681714621735324613718012418438119153
1037423722219749254241399140128192247

对于逆元表,先看逆元的定义。若a和b互为逆元,则有 a ∗ b = e a * b = e ab=e。用生成元表示为: g n ∗ g m = e = 1 g^n * g^m = e = 1 gngm=e=1。又因为 e = g 0 = g 255 e = g^0 = g^{255} e=g0=g255(循环,回头了)。所以 g k ∗ g ( 255 − k ) = g ( k + 255 − k ) = e g^k * g^{(255-k)} = g^{(k + 255 -k)} = e gkg(255k)=g(k+255k)=e。于是 g k g^k gk g ( 255 − k ) g^{(255-k)} g(255k) 互为逆元。对于多项式值val,求其逆元。可以先求val对应的g幂次是多少。即g的多少次方等于val。可以通过反向表查询, 设为 k k k。那么其逆元的幂次为 255 − k 255-k 255k。此时再通过正向表查询即可。实现代码如下:

int inverse_table[256];
 
for(i = 1; i < 256; ++i)//0没有逆元,所以从1开始
{
 int k = arc_table[i];
 k = 255 - k;
 k %= 255;//m_table的取值范围为 [0, 254]
 inverse_table[i] = table[k];
}

逆表 (十进制)
***1141246203821232092327941192176225
11618017075153439695886325320425564
5811090241857716820119310152214868
446914610824357102662425332111119187
29254551034549245105167100171198437
237925202763613519124623424081236
22941752117316654672447114522351147
12118315113316181186601821122086161250
131126127128150115190861551581492172472
222106501092161381321144220159136249220
2511244619514318410172382001874206231
12224312391711712011316514211861189188
114047163218212228151693983427252
122717499197219226234148139196213157248
17713214235198142071738782152279380
9135565210470314022115612516020526

求完三个表后,现在总结一下三个表下标和下标对应元素值的含义。

  • 对于正表,下标就是生成元g的指数,取值范围为 [0, 254]。下标对应元素就是 g k g^k gk得到的多项式值。取值范围为 [1, 255]。
  • 对于反表,下标就是g^k得到的多项式值,取值范围为 [1, 255]。下标对应的元素就是生成元 g 的指数,取值范围为 [0, 254]。
  • 对于逆表,下标值和下标对应元素的值互为逆元,取值范围都是 [1, 255]。k的逆元就是 inverse_table[k]。

有了这些表,现在再去求多项式相乘,那么超级简单了。代码如下:

    int mul(int x, int y)
    {
     if( !x || !y )
      return 0;
     
     return table[ (arc_table[x] + arc_table[y]) % 255];
    }
除法:

除法直接使用上面的逆元表即可,所以 a/b 等于 mul(a, inverse_table[b]); 这里b不能取 0。0 没有逆元,不能除于 0。

拉格朗日插值:

拉格朗日插值是什么,可以参考维基百科。拉格朗日插值的一个很常见应用是:知道了平面上的n个点的坐标值,现在求一个函数 f ( x ) f(x) f(x),它经过这n个点。

在实数里面,利用拉格朗日插值法是很容易求的。但对于 G F ( p ) GF(p) GF(p) G F ( p n ) GF(p^n) GF(pn),拉格朗日插值法就有点难了。一开始我甚至怀疑拉格朗日插值法能不能用于 G F ( p ) GF(p) GF(p) G F ( p n ) GF(p^n) GF(pn),毕竟这两个东西的运算规则是奇葩的(特别是 G F ( p n ) GF(p^n) GF(pn))。不得不说,拉格朗日更奇葩,他构造出来的拉格朗日插值法也能用于 G F ( p ) GF(p) GF(p) G F ( p n ) GF(p^n) GF(pn)

对于 G F ( p ) GF(p) GF(p) G F ( p n ) GF(p^n) GF(pn),拉格朗日插值法中的分母,直接用其逆元即可。计算起来也不是太难,用前面提到的逆元表更是容易。

拉格朗日插值多项式展开系数:

拉格朗日插值法中的分子就坑爹了。虽然展开后,有一些规律,但对于编程来说,是很麻烦的。

还好,在中国知网那里搜到了一篇文章,里面有讲到怎么把拉格朗日插值法中的分子展开成利于编程实现的形式。鉴于读者们可能不能在知网下载文章,所以我就把文章上传到csdn中。读者可以点这里下载,细看。这里就不讲了,直接给出实现代码。

#include <iostream>
#include<vector>
 
using namespace std;
 
 
int accumulate(const std::vector<int> &vec, int start, int end)
{
    int i, val = 0;
    while( start != end)
    {
        val += vec[start++];
    }
 
    return val;
}
 
 
std::vector<int> fun(std::vector<int>& vec)
{
    int i, j;
    int size = vec.size();
 
    std::vector<int> factor;
    std::vector<int> result(vec.size() + 1, 1);
 
    factor.resize(size, 1);
    for(j = 0; j < size; ++j)
    {
        std::vector<int> temp;
        for(i = 0; i < size - j; ++i)
        {
            temp.push_back(vec[i] * factor[i]);
        }
 
        result[j + 1] = accumulate(temp, 0, temp.size());
 
        for(i = 1; i < temp.size(); ++i)
            factor[i-1] = accumulate(temp, i, temp.size());
    }
 
    return result;
}
 
int main()
{
    int val[] = {2, 3, 5};
 
    std::vector<int> vec(val, val + sizeof(val)/sizeof(val[0]));
 
 //结果数组中,依次是高最次幂的系数,次高次幂的系数....一次幂的系数,常数项的系数
    std::vector<int> result = fun(vec);
 
    int i = 0;
    for(i = 0; i < result.size(); ++i)
        cout<<result[i]<<' ';
    cout<<endl;
 
 
    return 0;
}

需要注意的是,上面代码是那篇文章的直接实现,是在实数域里面的运算。需要修改才能用于 G F ( 2 8 ) GF(2^8) GF(28)。只需把代码里面的加法和乘法替换成 G F ( 2 8 ) GF(2^8) GF(28)的加法和乘法即可。

  • 9
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值