有限域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 
 }
}

输出:

正表(十进制)
1 3 5 15 17 51 85 255 26 46 114 150 161 248
95 225 56 72 216 115 149 164 247 2 6 10 30 34
229 52 92 228 55 89 235 38 106 190 217 112 144 171
83 245 4 12 20 60 68 204 79 209 104 184 211 110
76 212 103 169 224 59 77 215 98 166 241 8 24 40
131 158 185 208 107 189 220 127 129 152 179 206 73 219
181 196 87 249 16 48 80 240 11 29 39 105 187 214
254 25 43 125 135 146 173 236 47 113 147 174 233 32
251 22 58 78 210 109 183 194 93 231 50 86 250 21
195 94 226 61 71 201 64 192 91 237 44 116 156 191
159 186 213 100 172 239 42 126 130 157 188 223 122 142
155 182 193 88 232 35 101 175 234 37 111 177 200 67
252 31 33 99 165 244 7 9 27 45 119 153 176 203
69 207 74 222 121 139 134 145 168 227 62 66 198 81
18 54 90 238 41 123 141 140 143 138 133 148 167 242
57 75 221 124 132 151 162 253 28 36 108 180 199 82

这个正表,下标值等于生成元的指数,下标对应的元素值等于对应的多项式值。
例如:下标为 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;
反表 (十进制)
*** 0 25 1 50 2 26 198 75 199 27 104 51 238
100 4 224 14 52 141 129 239 76 113 8 200 248 105
125 194 29 181 249 185 39 106 77 228 166 114 154 201
101 47 138 5 33 15 225 36 18 240 130 69 53 147
150 143 219 189 54 208 206 148 19 92 210 241 64 70
102 221 253 48 191 6 139 98 179 37 226 152 34 136
126 110 72 195 163 182 30 66 58 107 40 84 250 133
43 121 10 21 155 159 94 202 78 212 172 229 243 115
175 88 168 80 244 234 214 116 79 174 233 213 231 230
44 215 117 122 235 22 11 245 89 203 95 176 156 169
127 12 246 111 23 196 73 236 216 67 31 45 164 118
204 187 62 90 251 96 177 134 59 82 161 108 170 85
151 178 135 144 97 190 220 252 188 149 207 205 55 63
83 57 132 60 65 162 109 71 20 42 158 93 86 242
68 17 146 217 35 32 46 137 180 124 184 38 119 153
103 74 237 222 197 49 254 24 13 99 140 128 192 247

对于逆元表,先看逆元的定义。若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];
}

逆表 (十进制)
*** 1 141 246 203 82 123 209 232 79 41 192 176 225
116 180 170 75 153 43 96 95 88 63 253 204 255 64
58 110 90 241 85 77 168 201 193 10 152 21 48 68
44 69 146 108 243 57 102 66 242 53 32 111 119 187
29 254 55 103 45 49 245 105 167 100 171 19 84 37
237 92 5 202 76 36 135 191 24 62 34 240 81 236
22 94 175 211 73 166 54 67 244 71 145 223 51 147
121 183 151 133 16 181 186 60 182 112 208 6 161 250
131 126 127 128 150 115 190 86 155 158 149 217 247 2
222 106 50 109 216 138 132 114 42 20 159 136 249 220
251 124 46 195 143 184 101 72 38 200 18 74 206 231
12 224 31 239 17 117 120 113 165 142 118 61 189 188
11 40 47 163 218 212 228 15 169 39 83 4 27 252
122 7 174 99 197 219 226 234 148 139 196 213 157 248
177 13 214 235 198 14 207 173 8 78 215 227 93 80
91 35 56 52 104 70 3 140 221 156 125 160 205 26

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

  • 对于正表,下标就是生成元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 
#include
 
using namespace std;
 
 
int accumulate(const std::vector &vec, int start, int end)
{
    int i, val = 0;
    while( start != end)
    {
        val += vec[start++];
    }
 
    return val;
}
 
 
std::vector fun(std::vector& vec)
{
    int i, j;
    int size = vec.size();
 
    std::vector factor;
    std::vector result(vec.size() + 1, 1);
 
    factor.resize(size, 1);
    for(j = 0; j < size; ++j)
    {
        std::vector 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 vec(val, val + sizeof(val)/sizeof(val[0]));
 
 //结果数组中,依次是高最次幂的系数,次高次幂的系数....一次幂的系数,常数项的系数
    std::vector result = fun(vec);
 
    int i = 0;
    for(i = 0; i < result.size(); ++i)
        cout<

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

你可能感兴趣的:(密码)