文档引用了《密码编码学与网络安全--原理和实践》里边的推导过程,如有不妥,请与我联系修改。
文档《FIPS 197》高级加密标准AES,里边有个S盒构造,涉及到了数论和有限域的一些概念,一脸懵逼,所以贱贱的研究了下,花了好久时间。
在网上找的S盒构造的详细步骤总是缺了点什么,要么步骤不详细,要么只贴了程序,难以搞清楚由几个基本概念一步一步推导出最终的S盒。最后,还是《密码编码学与网络安全--原理和实践》这本书讲得比较详细。教材果然还是经过精雕细琢过的,符合大部分人的认知过程。
这篇文章其一是记录下来这个学习步骤,其二是希望我的这篇能够更详细些。
先贴出来《FIPS 197》中对S盒构造的描述步骤:
就这两条。实际上是三个步骤,《密码编码学与网络安全--原理和实践》教材里会讲得更详细些。
一下都按照《密码编码学与网络安全--原理和实践》教材里边的三个步骤进行推导。
步骤1、3都比较浅显,即使没有数论和有限域概念,一样可以编程写出来。
步骤一:
根据行标号和列标号组合成16X16的二维数组,行标号作为高4bit,列标号作为低4bit;
生成代码如下:
1 for(i=0;i<0x10;i++)
2 {
3 for(j=0;j<0x10;j++)
4 {
5 s_box_ary[i][j] = ((i<<4)&0xF0) + (j&(0xF));
6 }
7 }
代码产生的数组:
1 0 1 2 3 4 5 6 7 8 9 A B C D E F
2 0 0 1 2 3 4 5 6 7 8 9 a b c d e f
3 1 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
4 2 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
5 3 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
6 4 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
7 5 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f
8 6 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f
9 7 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f
10 8 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f
11 9 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f
12 a a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af
13 b b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf
14 c c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf
15 d d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df
16 e e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef
17 f f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
本文重点叙述步骤2的推导。
步骤二:
这里边有三个概念:有限域、GF(2^8)、逆。
有限域:我的理解是,有一些元素构成了一个集合,集合中的一个或多个元素,进行某种运算,所得的结果仍然是集合中的元素。 元素,可以是具体的数字,也可以是字母,或是表达式,等等;某种运算,可以是加减乘除,或者逻辑运算,或者求余,或者是这几种运算的组合,等等。 这个定义当然很不严格,但是我觉得对于理解这个S盒推导够用了。
GF(2^8):GF()是代表一个有限域,2^8=256,是指这个有限域内的元素的个数,即256个。
举个是有限域的集合的例子吧。
GF(7)={0,1,2,3,4,5,6},它是关于任意两个元素的相加/乘积模7运算的有限域。特点是任意两个元素相加/乘积,对7取余数,这个余数仍然在GF(7)内。
截取《密码编码学与网络安全--原理和实践》中的例子:
此外,这个GF(7)的7叫做阶,特点是阶与域内的元素都互素(互质)。
在计算机中,一个字节是8位,0~255刚好是一个字节所能代表的所有数字,但是呢,GF(256),256对于0~255内的元素并不是每个都互素(互质),当以251为模时,251~255又不能用,造成浪费,所以不能直接使用上边的计算形式。但是我们还得必须用0~255这256个整数作为一个集合,通过某种运算构成有限域,所有只能把研究重点放在“某种运算”上。可能是为了区分GF(256),所以用GF(2^8)。
逆:乘法逆元。定义:GF(p), (a)、(b)、(a-1)都在GF(p)内,其中(a)、(a-1)互为乘法逆元,则有:[(a) X (a-1)] mod p = 1;
第二个步骤,就是 在步骤一得到的数组基础上,对每个元素 在 GF(2^8)有限域上求解出乘法逆元,在原位置替换该元素。
如何求解有限域上的逆元?《密码编码学与网络安全--原理和实践》中从欧几里得算法开始做知识铺垫,到扩展欧几里得算法。我们可以得出求乘法逆元的一个程序上可实现的方法。
d=gcd(a,b),d是a和b的最大公约数,或者叫最大公因子。求解步骤:
1、定义变量:r0, r1, r2
2、赋初值r0=a;r1=b;
3、求解r0、r1的余数:r3=r0 Mod r1;
4、更新变量:r0=r1;r1=r2;
5、从3开始重复,一直到求解的余数r1是0结束。
6、r0就是要求解的最大公约数。
long gcd(long a, long b) { long tmp; while(b) { tmp=a; a=b; b=tmp%b; } return a; }
欧几里得算法的关键是gcd(a,b)=gcd(b,(a mod b));为什么能成立?
可以证明:
当a>b,就有,a=q1 * b + r1,r1 = a - q1 * b;
假设 d=gcd(a,b),那么d分别是a和b的最大公因子,记作:d|a,d|b,所以:d|(a-q1 * b)=d|r1
所以有d=gcd(b,r1)=gcd(b, (a mod b));
《密码编码学与网络安全--原理和实践》里边讲解会更详细:
扩展欧几里得算法的计算步骤同欧几里得算法的步骤相似,由一个公式迭代计算,一直达到某个条件成立结束迭代,返回结果。
扩展欧几里得算法用来求解乘法逆元,为什么?
上边已经有了公式:[(a) * (a-1)] mod p = 1;
可以等效变换成:p * x + 1 = (a) * (a-1)
========> - p * x + (a) * (a-1) = 1
与ax+by=1的形式是不是很像?
与ax+by=gcd(a,b)=1的形式是不是很像?
gcd(a,b)=1,就是a、b互素即可。
可以看出,可以运用欧几里得算法的步骤计算乘法逆元,但是怎么计算呢?
这点我还是照搬《密码编码学与网络安全--原理和实践》里边的讲解步骤吧,我觉得不会比他讲得更好了。
就是这样,初始条件:(R-1) = a; R0=b; (X-1)=1;X0=0;(Y-1)=0;Y0=1;
迭代步骤:Rn=(Rn-2) Mod (Rn-1);Qn = [(Rn-2) /(Rn-1)] {(Rn-2) /(Rn-1)的商};Xn = (Xn-2) - (Xn-2) ;Yn = (Yn-2) - (Yn-2) 。
终止条件:Rn=1时,计算出来的Yn即是结果。
如果结果为负数,需要加上模值变为正数。
代码如下:
long multiplicativeInverse(long a, long b)
{
long r0,r1,r2,q1,x0,x1,x2,y0,y1,y2;
long d = gcd(a,b);
if((d!=1)&&(d!=-1))
{
printf("a、b不互质
");
return -1;
}
r0=a;
r1=b;
x0=1;
y0=0;
x1=0;
y1=1;
if((b==1)||(b==-1))
{
y2=y1;
}
while((r1!=1)&&(r1!=-1))
{
q1=r0/r1;
r2=r0%r1;
x2=x0-q1*x1;
y2=y0-q1*y1;
r0=r1;
r1=r2;
x0=x1;
x1=x2;
y0=y1;
y1=y2;
}
if(y2 < 0)
{
y2=a+y2;
}
return y2;
}
回归到GF(2^8)有限域,需要找到“某种运算”,使GF(2^8)有限域成立。这种运算是多项式除法运算。
多项式如下形式:
我们可以把GF(2^8)有限域内的每一个元素,按照下列方式写成多项式的形式:
设 字节a ∈ GF(2^8),写成二进制的形式a=b7b6b5b4b3b2b1b0,用bn代表a的每一位,其中n是二进制数中的位置;
那么,bn当作系数,n作为变量x的指数;
可以把一个字节写成:
这种形式。
举例:
0x9A=(b)10011010,写成多项式形式:x^7+x^4+x^3+x。
多项式除法运算,计算规则:
1、遵循代数基本规则中的普通多项式运算规则;
2、系数运算遵循以2为模的加法和乘法运算;(原话是:系数运算以p为模,即遵循有限域Zp上的运算规则);
3、如果乘法运算的结果是次数大于7(原文:n-1)的多项式,那么必须将其除以某个次数为8(原文:n)的即约多项式m(x)并取余式,对于多项式f(x),这个余数可表示为:即r(x) = f(x) mod m(x)。
高级加密标准AES使用有限域GF(2^8)上的运算,其中即约多项式m(x)=x^8 + x^4 + x^3 + x + 1;
举例:
m(x)=x^8 + x^4 + x^3 + x + 1;
f(x) =x^6 + x^4 + x^2 + x + 1;g(x) =x^7 + x + 1;
f(x) * g(x) = (x^6 + x^4 + x^2 + x + 1) * (x^7 + x + 1)
= 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
r(x) = [f(x) * g(x)] mod m(x) => m(x) * q(x) + r(x) = f(x) * g(x):
q(x) = x^5 + x^3;r(x) = x^7 + x^6 + 1。
上文已经讲到:扩展欧几里得算法用来求解乘法逆元。
(b-1)*b mod a = 1; => ax+by=1=gcd(a, b)
把a、b用多项式替代,形式如下:
b-1(x) * b(x) mod m(x) = 1 => m(x)v(x) + b(x)w(x) = 1 = gcd(m(x), b(x))
直接引用上边求乘法逆元的步骤,用多项式直接替代数值计算:
重述如下:
1、把带求解的字节变换成多项式形式b(x);
2、初始条件:
(R-1) = m(x); R0=b(x);
(v-1)(x)=1; v0(x)=0;
(w-1)(x)=0; w0(x)=1;
3、迭代步骤:
Rn(x)=(Rn-2)(x) Mod (Rn-1)(x);
Qn(x) = [(Rn-2)(x) / (Rn-1)(x)] 即:{(Rn-2) /(Rn-1)的商};
vn(x) = (vn-2)(x) - Qn(x)*(vn-2)(x) ;
wn(x) = (wn-2)(x) - Qn(x)*(wn-2) 。
4、终止条件:
Rn(x)=1时,计算出来的wn(x)即是结果多项式。
5、把wn(x)变换回字节。
上述步骤中,需要专门的多项式乘法、多项式除法、多项式求余运算的实现函数。
多项式乘法函数:
//GF(2^8)的多项式乘法 uint16_t polynomialMutil(uint8_t a, uint8_t b) { uint16_t tmp[8]={0}; uint8_t i; for(i=0;i<8;i++) { tmp[i] = (a<<i)*((b>>i)&0x1); } tmp[0] = tmp[0] ^ tmp[1] ^ tmp[2] ^ tmp[3] ^ tmp[4] ^ tmp[5] ^ tmp[6] ^ tmp[7]; return tmp[0]; }
多项式除法函数:
//找到最高位 uint8_t findHigherBit(uint16_t val) { int i=0; while(val) { i++; val = val>>1; } return i; } //GF(2^8)的多项式除法 uint8_t gf28_div(uint16_t div_ed, uint16_t div, uint16_t *remainder) { uint16_t r0=0; uint8_t qn=0; int bitCnt=0; r0=div_ed; bitCnt = findHigherBit(r0)-findHigherBit(div); while(bitCnt>=0) { qn = qn | (1<<bitCnt); r0 = r0 ^ (div<<bitCnt); bitCnt = findHigherBit(r0)-findHigherBit(div); } *remainder = r0; return qn; }
多项式的扩展欧几里得算法:
//GF(2^8)多项式的扩展欧几里得算法 uint8_t extEuclidPolynomial(uint8_t a, uint16_t m) { uint16_t r0, r1, r2; uint8_t qn, v0, v1, v2, w0, w1, w2; r0=m; r1=a; v0=1; v1=0; w0=0; w1=1; while(r1!=1) { qn=gf28_div(r0, r1, &r2); v2=v0^polynomialMutil(qn, v1); w2=w0^polynomialMutil(qn, w1); r0=r1; r1=r2; v0=v1; v1=v2; w0=w1; w1=w2; } return w1; }
至此,S盒变换的第二步骤实现完成。
根据 扩展欧几里得算法,得到的中间状态的S盒如下:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 0 1 8d f6 cb 52 7b d1 e8 4f 29 c0 b0 e1 e5 c7 1 74 b4 aa 4b 99 2b 60 5f 58 3f fd cc ff 40 ee b2 2 3a 6e 5a f1 55 4d a8 c9 c1 a 98 15 30 44 a2 c2 3 2c 45 92 6c f3 39 66 42 f2 35 20 6f 77 bb 59 19 4 1d fe 37 67 2d 31 f5 69 a7 64 ab 13 54 25 e9 9 5 ed 5c 5 ca 4c 24 87 bf 18 3e 22 f0 51 ec 61 17 6 16 5e af d3 49 a6 36 43 f4 47 91 df 33 93 21 3b 7 79 b7 97 85 10 b5 ba 3c b6 70 d0 6 a1 fa 81 82 8 83 7e 7f 80 96 73 be 56 9b 9e 95 d9 f7 2 b9 a4 9 de 6a 32 6d d8 8a 84 72 2a 14 9f 88 f9 dc 89 9a a fb 7c 2e c3 8f b8 65 48 26 c8 12 4a ce e7 d2 62 b c e0 1f ef 11 75 78 71 a5 8e 76 3d bd bc 86 57 c b 28 2f a3 da d4 e4 f a9 27 53 4 1b fc ac e6 d 7a 7 ae 63 c5 db e2 ea 94 8b c4 d5 9d f8 90 6b e b1 d d6 eb c6 e cf ad 8 4e d7 e3 5d 50 1e b3 f 5b 23 38 34 68 46 3 8c dd 9c 7d a0 cd 1a 41 1c
步骤三:S盒字节变换和逆S盒字节变换:
//S盒字节变换 uint8_t byteTransformation(uint8_t a, uint8_t x) { uint8_t tmp[8]={0}; for(uint8_t i=0;i<8;i++) { tmp[i]= (((a>>i)&0x1)^((a>>((i+4)%8))&0x1)^((a>>((i+5)%8))&0x1)^((a>>((i+6)%8))&0x1)^((a>>((i+7)%8))&0x1)^((x>>i)&0x1)) << i; } tmp[0] = tmp[0]+tmp[1]+tmp[2]+tmp[3]+tmp[4]+tmp[5]+tmp[6]+tmp[7]; return tmp[0]; } //逆S盒字节变换 uint8_t invByteTransformation(uint8_t a, uint8_t x) { uint8_t tmp[8]={0}; for(uint8_t i=0;i<8;i++) { tmp[i]= (((a>>((i+2)%8))&0x1)^((a>>((i+5)%8))&0x1)^((a>>((i+7)%8))&0x1)^((x>>i)&0x1)) << i; } tmp[0] = tmp[0]+tmp[1]+tmp[2]+tmp[3]+tmp[4]+tmp[5]+tmp[6]+tmp[7]; return tmp[0]; }
S盒变换代码:
//S盒产生 void s_box_gen(void) { uint8_t i,j; uint8_t s_box_ary[16][16] = {0}; //初始化S盒 for(i=0;i<0x10;i++) { for(j=0;j<0x10;j++) { s_box_ary[i][j] = ((i<<4)&0xF0) + (j&(0xF)); } } printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); for(i=0;i<0x10;i++) { printf(" %2x",i); for(j=0;j<0x10;j++) { printf(" %2x",s_box_ary[i][j]); } } //求在GF(2^8)域上的逆,0映射到自身 printf(" "); for(i=0;i<0x10;i++) { for(j=0;j<0x10;j++) { if(s_box_ary[i][j] != 0) { s_box_ary[i][j] = extEuclidPolynomial(s_box_ary[i][j],0x11B); } } } printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); for(i=0;i<0x10;i++) { printf(" %2x",i); for(j=0;j<0x10;j++) { printf(" %2x",s_box_ary[i][j]); } } //对每个字节做变换 for(i=0;i<0x10;i++) { for(j=0;j<0x10;j++) { s_box_ary[i][j]=byteTransformation(s_box_ary[i][j], 0x63); } } printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); for(i=0;i<0x10;i++) { printf(" %2x",i); for(j=0;j<0x10;j++) { printf(" %2x",s_box_ary[i][j]); } } }
输出如下:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 1 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 2 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 3 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 4 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 5 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 6 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 7 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 8 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 9 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 0 1 8d f6 cb 52 7b d1 e8 4f 29 c0 b0 e1 e5 c7 1 74 b4 aa 4b 99 2b 60 5f 58 3f fd cc ff 40 ee b2 2 3a 6e 5a f1 55 4d a8 c9 c1 a 98 15 30 44 a2 c2 3 2c 45 92 6c f3 39 66 42 f2 35 20 6f 77 bb 59 19 4 1d fe 37 67 2d 31 f5 69 a7 64 ab 13 54 25 e9 9 5 ed 5c 5 ca 4c 24 87 bf 18 3e 22 f0 51 ec 61 17 6 16 5e af d3 49 a6 36 43 f4 47 91 df 33 93 21 3b 7 79 b7 97 85 10 b5 ba 3c b6 70 d0 6 a1 fa 81 82 8 83 7e 7f 80 96 73 be 56 9b 9e 95 d9 f7 2 b9 a4 9 de 6a 32 6d d8 8a 84 72 2a 14 9f 88 f9 dc 89 9a a fb 7c 2e c3 8f b8 65 48 26 c8 12 4a ce e7 d2 62 b c e0 1f ef 11 75 78 71 a5 8e 76 3d bd bc 86 57 c b 28 2f a3 da d4 e4 f a9 27 53 4 1b fc ac e6 d 7a 7 ae 63 c5 db e2 ea 94 8b c4 d5 9d f8 90 6b e b1 d d6 eb c6 e cf ad 8 4e d7 e3 5d 50 1e b3 f 5b 23 38 34 68 46 3 8c dd 9c 7d a0 cd 1a 41 1c 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 63 7c 77 7b f2 6b 6f c5 30 1 67 2b fe d7 ab 76 1 ca 82 c9 7d fa 59 47 f0 ad d4 a2 af 9c a4 72 c0 2 b7 fd 93 26 36 3f f7 cc 34 a5 e5 f1 71 d8 31 15 3 4 c7 23 c3 18 96 5 9a 7 12 80 e2 eb 27 b2 75 4 9 83 2c 1a 1b 6e 5a a0 52 3b d6 b3 29 e3 2f 84 5 53 d1 0 ed 20 fc b1 5b 6a cb be 39 4a 4c 58 cf 6 d0 ef aa fb 43 4d 33 85 45 f9 2 7f 50 3c 9f a8 7 51 a3 40 8f 92 9d 38 f5 bc b6 da 21 10 ff f3 d2 8 cd c 13 ec 5f 97 44 17 c4 a7 7e 3d 64 5d 19 73 9 60 81 4f dc 22 2a 90 88 46 ee b8 14 de 5e b db a e0 32 3a a 49 6 24 5c c2 d3 ac 62 91 95 e4 79 b e7 c8 37 6d 8d d5 4e a9 6c 56 f4 ea 65 7a ae 8 c ba 78 25 2e 1c a6 b4 c6 e8 dd 74 1f 4b bd 8b 8a d 70 3e b5 66 48 3 f6 e 61 35 57 b9 86 c1 1d 9e e e1 f8 98 11 69 d9 8e 94 9b 1e 87 e9 ce 55 28 df f 8c a1 89 d bf e6 42 68 41 99 2d f b0 54 bb 16
逆S盒变换代码:
//逆S盒产生 void inv_s_box_gen(void) { uint8_t i,j; uint8_t s_box_ary[16][16] = {0}; uint8_t b=0, bb=0; //初始化S盒 for(i=0;i<0x10;i++) { for(j=0;j<0x10;j++) { s_box_ary[i][j] = ((i<<4)&0xF0) + (j&(0xF)); } } printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); for(i=0;i<0x10;i++) { printf(" %2x",i); for(j=0;j<0x10;j++) { printf(" %2x",s_box_ary[i][j]); } } //对每个字节做变换 for(i=0;i<0x10;i++) { for(j=0;j<0x10;j++) { s_box_ary[i][j]=invByteTransformation(s_box_ary[i][j], 0x05); } } printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); for(i=0;i<0x10;i++) { printf(" %2x",i); for(j=0;j<0x10;j++) { printf(" %2x",s_box_ary[i][j]); } } //求在GF(2^8)域上的逆,0映射到自身 printf(" "); for(i=0;i<0x10;i++) { for(j=0;j<0x10;j++) { if(s_box_ary[i][j] != 0) { s_box_ary[i][j] = extEuclidPolynomial(s_box_ary[i][j],0x11B); } } } printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); for(i=0;i<0x10;i++) { printf(" %2x",i); for(j=0;j<0x10;j++) { printf(" %2x",s_box_ary[i][j]); } } }
输出如下:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 1 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 2 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 3 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 4 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 5 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 6 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 7 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 8 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 9 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 5 4f 91 db 2c 66 b8 f2 57 1d c3 89 7e 34 ea a0 1 a1 eb 35 7f 88 c2 1c 56 f3 b9 67 2d da 90 4e 4 2 4c 6 d8 92 65 2f f1 bb 1e 54 8a c0 37 7d a3 e9 3 e8 a2 7c 36 c1 8b 55 1f ba f0 2e 64 93 d9 7 4d 4 97 dd 3 49 be f4 2a 60 c5 8f 51 1b ec a6 78 32 5 33 79 a7 ed 1a 50 8e c4 61 2b f5 bf 48 2 dc 96 6 de 94 4a 0 f7 bd 63 29 8c c6 18 52 a5 ef 31 7b 7 7a 30 ee a4 53 19 c7 8d 28 62 bc f6 1 4b 95 df 8 20 6a b4 fe 9 43 9d d7 72 38 e6 ac 5b 11 cf 85 9 84 ce 10 5a ad e7 39 73 d6 9c 42 8 ff b5 6b 21 a 69 23 fd b7 40 a d4 9e 3b 71 af e5 12 58 86 cc b cd 87 59 13 e4 ae 70 3a 9f d5 b 41 b6 fc 22 68 c b2 f8 26 6c 9b d1 f 45 e0 aa 74 3e c9 83 5d 17 d 16 5c 82 c8 3f 75 ab e1 44 e d0 9a 6d 27 f9 b3 e fb b1 6f 25 d2 98 46 c a9 e3 3d 77 80 ca 14 5e f 5f 15 cb 81 76 3c e2 a8 d 47 99 d3 24 6e b0 fa 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 52 9 6a d5 30 36 a5 38 bf 40 a3 9e 81 f3 d7 fb 1 7c e3 39 82 9b 2f ff 87 34 8e 43 44 c4 de e9 cb 2 54 7b 94 32 a6 c2 23 3d ee 4c 95 b 42 fa c3 4e 3 8 2e a1 66 28 d9 24 b2 76 5b a2 49 6d 8b d1 25 4 72 f8 f6 64 86 68 98 16 d4 a4 5c cc 5d 65 b6 92 5 6c 70 48 50 fd ed b9 da 5e 15 46 57 a7 8d 9d 84 6 90 d8 ab 0 8c bc d3 a f7 e4 58 5 b8 b3 45 6 7 d0 2c 1e 8f ca 3f f 2 c1 af bd 3 1 13 8a 6b 8 3a 91 11 41 4f 67 dc ea 97 f2 cf ce f0 b4 e6 73 9 96 ac 74 22 e7 ad 35 85 e2 f9 37 e8 1c 75 df 6e a 47 f1 1a 71 1d 29 c5 89 6f b7 62 e aa 18 be 1b b fc 56 3e 4b c6 d2 79 20 9a db c0 fe 78 cd 5a f4 c 1f dd a8 33 88 7 c7 31 b1 12 10 59 27 80 ec 5f d 60 51 7f a9 19 b5 4a d 2d e5 7a 9f 93 c9 9c ef e a0 e0 3b 4d ae 2a f5 b0 c8 eb bb 3c 83 53 99 61 f 17 2b 4 7e ba 77 d6 26 e1 69 14 63 55 21 c 7d
以上代码肯定不是最优代码,欢迎拍砖,并在留言区留下您宝贵意见,谢谢!