使用乘法表计算GF(2^8)中的乘法
2013-10-12 15:02
225 查看
作业要求:
构造256字节的表格X2,使得X2[i] ={02}*i,i是GF(2^8)中的元素。将此表运用于GF(2^8)的乘法,与之前作业的乘法对比,看谁的速度快?
直奔主题吧。
先看看主函数:
程序流程:构造乘法表X2 —— 初始化工作 ——
输入数a和数b —— 用查表法计算乘法结果 —— 输出运算结果和运算所用时间 —— 是否循环执行程序。
下面分段来看。
1.构造乘法表X2
为了便于使用该表,将其声明为全局静态变量:
构造表的函数:
由于00-7F的运算结果为00-FE,没有溢出,所以可以作为表的前半部分初始化。80-FF全部溢出,需要先左移1位再异或1BH,所以作为表的后半部分初始化。
注意由于xtable二维数组中的元素均为指针类型,如果对于每一次赋值都使用xtable[j][k]=arr;那么xtable中所有指针都指向同一块内存块arr,由于arr[2]最后的结果为14 5,所以所有xtable中的元素都指向{14, 5}数组,因此必须首先new一个xtable[j][k]指针,其长度为2,然后分别对其中的两个元素赋值。
在后半部分初始化中,例如255,先要用intToArr函数将其转化为两位的16进制数组(每一位都是十进制数),即{15,15}:
然后用decToBin函数将两位16进制数组转换为二进制数组,即11111111:
先将转换后的数组bin左移1位,再作溢出处理,即使用leftshift1函数:
将转换的二进制数组转换为两位16进制数组:
注意,为什么每一位都是十进制数呢(例如F对应15),原因是查表时行号和列号是直接用十进制查的,这样便于迭代查表。虽然转换麻烦了点,但是在做乘法查表时不用再转换。
如果表格为两位16进制字符数组,那么结果为:
2.用查表法计算乘法结果
这里沿用了移位乘法的程序框架,只是计算的方法不同而已:
leftshift函数为:
state为全局静态变量:
其中一次查表就相当于一次左移(包括溢出处理)运算。
这里优化程序的思想和在GF(2^8)下可做任意两个数乘法的程序(一)中的乘法思想是一样的,为了避免每次都要从头开始查表,每次运算结果都用temp保存起来,用于下一次运算的查表。
例如:求a * 00000110,从右边开始,由于b[6]=1,所以先查表一次计算得到a * 2,并用temp保存a * 2的结果,在求a * 4的时候,可以直接用temp(也就是a * 2的结果)查表一次得到结果,如果没有用到temp的话,那么* 4必须要查表2次。最后将a * 2和 a * 4异或即可。
如果a中1的个数越多,那么优化后的程序效率更高(单纯就乘法该过程看),例如如果是a * 11111111,那么不优化的情况下要查表(每次查表对应一次乘2)1+2+3+4+5+6+7=28次。如果按以上的方法优化了,只需要查表7次就可以了。
至于输入数a和数b ,异或加运算等函数和在GF(2^8)下可做任意两个数乘法的程序(一)给出的一样,不再赘述。
运行一下,比较两者的运行效率:
乘法表运算时间结果:
直接乘法运算时间结果:
首先有一点要强调的是,由于CPU每个时刻工作的速度和时钟频率等都处于动态变化当中,所以以上数据不能算绝对准确,加上程序员不同的算法也会导致程序运行的时间出现差异,因此以上数据仅仅用于对比,而且也不能算得上绝对准确。
比较之下,乘法表运算多了一些转换的工作(包括leftshift函数,因为其中的异或运算必须要使用二进制数组,而且输出结果形式也是二进制形式),也就是在乘法表的条件差于移位乘法的情况下其效率仍然高于后者,所以也得出了老师想向我们传递的信息:GF(2^8)中的乘法运算查表运算的效率高于直接运算。
当初AES的设计者就是用该方法来对乘法进行加速(另外乘法可能会受到时间分析攻击),另外表所使用的空间开销并不大,用查表计算乘法结果的方法优于直接使用乘法。
最后,小结一个问题:
对于调用函数,如果传入参数的是数组指针并且在函数中修改了参数的值,那么数组的值将相应变化。如果传入参数的是基本类型并且在函数中修改了参数的值,基本类型变量的值却不会相应发生变化。例如:
运行结果:
道理很简单,因为在传入指针到参数后,若参数值被修改,那么指针所指向的内存块的值也相应被修改。而基本类型变量的参数传入则是另外复制一份传值。
构造256字节的表格X2,使得X2[i] ={02}*i,i是GF(2^8)中的元素。将此表运用于GF(2^8)的乘法,与之前作业的乘法对比,看谁的速度快?
直奔主题吧。
先看看主函数:
int main() { // -- 构造乘法表X2 -- createX2(); // -- 进入乘法运算程序 -- int abin[8], bbin[8], c[8]; LARGE_INTEGER start_t, stop_t, freq; // start_t表示计时开始时间,stop_t表示计时结束时间,freq为计时器的时钟频率 double exe_time; QueryPerformanceFrequency(&freq); // fprintf(stdout, "The frequency of your pc is %d.\n", freq.QuadPart); char choose='y'; while(choose=='y'||choose=='Y') { // -- 清空之前的计算结果 -- for(int i=0; i<8; i++) { c[i]=0; // 清空结果 } state=0; // 清空当前查表次数 // -- 输入乘数a和b -- if(input(abin, 'a')==0) // 如果输入a没有出错 { if(input(bbin, 'b')==0) // 如果输入b没有出错 { // -- 程序开始执行,开始计时 -- QueryPerformanceCounter(&start_t); // -- 将输入的二进制数组转换为十进制数组并保存到temp中 -- int temp[2]; temp[0]=binToDec(abin, 0); temp[1]=binToDec(abin, 1); // -- 执行乘法运算 -- for(i=7; i>=0; i--) { if(bbin[i]==1) { xor(c, leftshift(abin, 7-i, temp)); // 求和 } } // -- 输出运算结果 -- cout<<"运算结果为:"; for(i=0; i<8; i++) { cout<<c[i]; } // -- 程序结束执行,结束计时 -- QueryPerformanceCounter(&stop_t); exe_time = 1e3*(stop_t.QuadPart-start_t.QuadPart)/freq.QuadPart; cout<<endl; fprintf(stdout, "计算用时:%fms.\n", exe_time); } } // -- 是否继续执行程序 -- cout<<endl<<"是否继续?y/n:"; choose=cin.get(); if(cin.get()==10) { // 跳过输入y/n后的回车键,防止影响下面的输入 } } return 0; }
程序流程:构造乘法表X2 —— 初始化工作 ——
输入数a和数b —— 用查表法计算乘法结果 —— 输出运算结果和运算所用时间 —— 是否循环执行程序。
下面分段来看。
1.构造乘法表X2
为了便于使用该表,将其声明为全局静态变量:
static int *xtable[16][16]; // 乘法表X2
构造表的函数:
/* 建造乘法表X2 */ void createX2() { int temp=0, arr[2], bin[8]; int j, k; // 初始化xtable前半部分:00-7F for(j=0; j<8; j++) { for(k=0; k<16; k++) { intToArr(temp*2, arr); temp++; xtable[j][k]=new int[2]; xtable[j][k][0]=arr[0]; xtable[j][k][1]=arr[1]; } } // 初始化xtable后半部分:80-FF for(j=8; j<16; j++) { for(k=0; k<16; k++) { intToArr(temp, arr); decToBin(arr, bin); leftshift1(bin); arr[0]=binToDec(bin, 0); arr[1]=binToDec(bin, 1); xtable[j][k]=new int[2]; xtable[j][k][0]=arr[0]; xtable[j][k][1]=arr[1]; temp++; } } }
由于00-7F的运算结果为00-FE,没有溢出,所以可以作为表的前半部分初始化。80-FF全部溢出,需要先左移1位再异或1BH,所以作为表的后半部分初始化。
注意由于xtable二维数组中的元素均为指针类型,如果对于每一次赋值都使用xtable[j][k]=arr;那么xtable中所有指针都指向同一块内存块arr,由于arr[2]最后的结果为14 5,所以所有xtable中的元素都指向{14, 5}数组,因此必须首先new一个xtable[j][k]指针,其长度为2,然后分别对其中的两个元素赋值。
在后半部分初始化中,例如255,先要用intToArr函数将其转化为两位的16进制数组(每一位都是十进制数),即{15,15}:
/* 将整数转换为数组,例如78=4 14 * 参数temp为要转换的整数 * 参数a[]为转换结果 */ void intToArr(int temp, int a[]) { a[1]=temp%16; a[0]=(temp-a[1])/16; }
然后用decToBin函数将两位16进制数组转换为二进制数组,即11111111:
/* 将2位十进制数组转换为8位二进制数组 * 参数d[]为要转换的十进制数组 * 参数bin[]为转换后的二进制数组 */ void decToBin(int d[], int bin[]) { int h0=d[0]; int h1=d[1]; int i=3; while(i>=0) { bin[i--]=h0%2; h0/=2; } i=7; while(i>=4) { bin[i--]=h1%2; h1/=2; } }
先将转换后的数组bin左移1位,再作溢出处理,即使用leftshift1函数:
/* 乘2,左移1位,如果溢出则异或1BH */ void leftshift1(int a[]) { int temp=a[0]; for(int i=0; i<7; i++) { a[i]=a[i+1]; } a[7]=0; if(temp==1) // 溢出处理 { int mod[8]={0, 0, 0, 1, 1, 0, 1, 1}; xor(a, mod); } }
将转换的二进制数组转换为两位16进制数组:
/* 将4位二进制数组转换为十进制数 * 参数b[]为要进行转换的数组 * 参数bit用于判断高低位,0为高位,1为低位 * 返回结果为转换后的十进制数 */ int binToDec(int b[], int bit) { int n=8, i=0, sum=0; if(bit==0) // 高四位二进制转换十进制 i=0; else if(bit==1) // 低四位二进制转换十进制 i=4; else return -1; // 4位二进制转换十进制,例如1011=11 while(n>=1) { sum+=(n*b[i]); i++; n/=2; } return sum; }
注意,为什么每一位都是十进制数呢(例如F对应15),原因是查表时行号和列号是直接用十进制查的,这样便于迭代查表。虽然转换麻烦了点,但是在做乘法查表时不用再转换。
如果表格为两位16进制字符数组,那么结果为:
static char *sbox[16][16]={ {"00", "02", "04", "06", "08", "0A", "0C", "0E", "10", "12", "14", "16", "18", "1A", "1C", "1E"}, // 0 {"20", "22", "24", "26", "28", "2A", "2C", "2E", "30", "32", "34", "36", "38", "3A", "3C", "3E"}, // 1 {"40", "42", "44", "46", "48", "4A", "4C", "4E", "50", "52", "54", "56", "58", "5A", "5C", "5E"}, // 2 {"60", "62", "64", "66", "68", "6A", "6C", "6E", "70", "72", "74", "76", "78", "7A", "7C", "7E"}, // 3 {"80", "82", "84", "86", "88", "8A", "8C", "8E", "90", "92", "94", "96", "98", "9A", "9C", "9E"}, // 4 {"A0", "A2", "A4", "A6", "A8", "AA", "AC", "AE", "B0", "B2", "B4", "B6", "B8", "BA", "BC", "BE"}, // 5 {"C0", "C2", "C4", "C6", "C8", "CA", "CC", "CE", "D0", "D2", "D4", "D6", "D8", "DA", "DC", "DE"}, // 6 {"E0", "E2", "E4", "E6", "E8", "EA", "EC", "EE", "F0", "F2", "F4", "F6", "F8", "FA", "FC", "FE"}, // 7 {"1B", "19", "1F", "1D", "13", "11", "17", "15", "0B", "09", "0F", "0D", "03", "01", "07", "05"}, // 8 {"3B", "39", "3F", "3D", "33", "31", "37", "35", "2B", "29", "2F", "2D", "23", "21", "27", "25"}, // 9 {"5B", "59", "5F", "5D", "53", "51", "57", "55", "4B", "49", "4F", "4D", "43", "41", "47", "45"}, // A {"7B", "79", "7F", "7D", "73", "71", "77", "75", "6B", "69", "6F", "6D", "63", "61", "67", "65"}, // B {"9B", "99", "9F", "9D", "93", "91", "97", "95", "8B", "89", "8F", "8D", "83", "81", "87", "85"}, // C {"BB", "B9", "BF", "BD", "B3", "B1", "B7", "B5", "AB", "A9", "AF", "AD", "A3", "A1", "A7", "A5"}, // D {"DB", "D9", "DF", "DD", "D3", "D1", "D7", "D5", "CB", "C9", "CF", "CD", "C3", "C1", "C7", "C5"}, // E {"FB", "F9", "FF", "FD", "F3", "F1", "F7", "F5", "EB", "E9", "EF", "ED", "E3", "E1", "E7", "E5"} // F // 0 1 2 3 4 5 6 7 8 9 A B C D E F };
2.用查表法计算乘法结果
这里沿用了移位乘法的程序框架,只是计算的方法不同而已:
// -- 执行乘法运算 -- for(i=7; i>=0; i--) { if(bbin[i]==1) { xor(c, leftshift(abin, 7-i, temp)); // 求和 } }
leftshift函数为:
/* 计算a[]乘上2的len次幂的结果 * 参数bin[]为要进行移位的数组 * 参数len为查表的次数 * 参数temp[]用于保存乘法运算结果 * 返回结果为乘2的运算结果 */ int *leftshift(int bin[], int len, int temp[]) { if(len==0) { state=0; return bin; } int row, column; for(state; state<len; state++) { row=temp[0]; column=temp[1]; temp[0]=xtable[row][column][0]; temp[1]=xtable[row][column][1]; } decToBin(temp, bin); // 将16进制的temp转换为二进制数组bin return bin; }
state为全局静态变量:
static int state=0; // 用于记录左移的位数,也就是已经查表的次数
其中一次查表就相当于一次左移(包括溢出处理)运算。
这里优化程序的思想和在GF(2^8)下可做任意两个数乘法的程序(一)中的乘法思想是一样的,为了避免每次都要从头开始查表,每次运算结果都用temp保存起来,用于下一次运算的查表。
例如:求a * 00000110,从右边开始,由于b[6]=1,所以先查表一次计算得到a * 2,并用temp保存a * 2的结果,在求a * 4的时候,可以直接用temp(也就是a * 2的结果)查表一次得到结果,如果没有用到temp的话,那么* 4必须要查表2次。最后将a * 2和 a * 4异或即可。
如果a中1的个数越多,那么优化后的程序效率更高(单纯就乘法该过程看),例如如果是a * 11111111,那么不优化的情况下要查表(每次查表对应一次乘2)1+2+3+4+5+6+7=28次。如果按以上的方法优化了,只需要查表7次就可以了。
至于输入数a和数b ,异或加运算等函数和在GF(2^8)下可做任意两个数乘法的程序(一)给出的一样,不再赘述。
运行一下,比较两者的运行效率:
乘法表运算时间结果:
直接乘法运算时间结果:
首先有一点要强调的是,由于CPU每个时刻工作的速度和时钟频率等都处于动态变化当中,所以以上数据不能算绝对准确,加上程序员不同的算法也会导致程序运行的时间出现差异,因此以上数据仅仅用于对比,而且也不能算得上绝对准确。
比较之下,乘法表运算多了一些转换的工作(包括leftshift函数,因为其中的异或运算必须要使用二进制数组,而且输出结果形式也是二进制形式),也就是在乘法表的条件差于移位乘法的情况下其效率仍然高于后者,所以也得出了老师想向我们传递的信息:GF(2^8)中的乘法运算查表运算的效率高于直接运算。
当初AES的设计者就是用该方法来对乘法进行加速(另外乘法可能会受到时间分析攻击),另外表所使用的空间开销并不大,用查表计算乘法结果的方法优于直接使用乘法。
最后,小结一个问题:
对于调用函数,如果传入参数的是数组指针并且在函数中修改了参数的值,那么数组的值将相应变化。如果传入参数的是基本类型并且在函数中修改了参数的值,基本类型变量的值却不会相应发生变化。例如:
#include <iostream> using namespace std; void setTemp(int temp) { temp=1; } void setArr(int a[]) { a[0]=1; a[1]=1; } void setBool(bool b) { b=false; } int main() { int temp=0; bool b=true; int arr[2]={0,0}; setTemp(temp); setArr(arr); cout<<"temp="<<temp<<endl; cout<<"b="<<b<<endl; cout<<"arr[0]="<<arr[0]<<endl; cout<<"arr[1]="<<arr[1]<<endl; }
运行结果:
道理很简单,因为在传入指针到参数后,若参数值被修改,那么指针所指向的内存块的值也相应被修改。而基本类型变量的参数传入则是另外复制一份传值。
相关文章推荐
- NumPy中的乘法运算符 * 指示按元素计算,矩阵乘法可以使用 dot 函数或创建矩阵对象实现
- 有限域GF(2^8)内乘法代码实现以及原理
- GF(2^8)乘法
- GF(2^8)乘法优化
- android 使用intent传递参数实现乘法计算
- 08-c不使用乘法运算符计算2*16
- android 使用intent传递参数实现乘法计算
- 使用快速傅里叶变换计算大整数乘法
- LeetCode 29 Divide Two Integers (不使用乘法,除法,求模计算两个数的除法)
- 使用快速傅里叶变换计算大整数乘法
- 有限域GF(2^8)内乘法代码实现以及原理
- hibernate使用Criteria计算乘法
- 在GF(2^8)下可做任意两个数乘法的程序
- 使用快速傅里叶变换计算大整数乘法
- 使用快速傅里叶变换计算大整数乘法-代码
- 量化-使用python计算各类移动平均线
- 如何使用snmp计算接口带宽
- 使用EmguCv计算包围物体的最小圆与最小可旋转矩形和不可选择矩形
- 使用BigDecimal进行科学计算表示方式的转换
- 使用类计算矩形的面积