使用乘法表计算GF(2^8)中的乘法

作业要求:
构造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;
}
运行结果:


道理很简单,因为在传入指针到参数后,若参数值被修改,那么指针所指向的内存块的值也相应被修改。而基本类型变量的参数传入则是另外复制一份传值。







  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在扩展域GF(2^8)计算是基于多项式求模的运算。GF(2^8)是由2的8次扩展得到的有限域,也被称为Galois域。 在GF(2^8),元素由8位二进制表示,其第8位是最高位。每个元素可以看作是一个多项式,其系数为0或1,表示不同的二进制位。 加法运算在GF(2^8)可以通过逐位异或实现。例如,将两个元素相加A(x)和B(x),需要将对应的二进制位逐位异或,生成C(x) = A(x) + B(x)。这样可以得到一个新的元素C(x)。 乘法运算在GF(2^8)是通过多项式乘法和多项式求模实现的。例如,将两个元素相乘A(x)和B(x),需要将A(x)和B(x)的多项式进行乘法运算,得到D(x) = A(x) * B(x)。然后,将D(x)与一个固定的生成多项式G(x)进行求模运算,得到一个新的元素E(x)。 除法运算在GF(2^8)是通过求逆元素和乘法运算实现的。对于一个元素A(x),要找到其逆元素A^(-1)(x),需要在GF(2^8)寻找一个元素B(x),使得A(x) * B(x) = 1。这样,除法运算可以转化为乘法运算。 在GF(2^8),还有其他运算,如指数运算和对数运算。指数运算将一个元素A(x)提升到一个非负整数n次幂,得到一个新的元素B(x) = A(x)^n。对数运算将一个元素A(x)转化为一个非负整数n,使得A(x) = B(x)^n。 总之,在GF(2^8)计算主要包括加法、乘法、除法、指数运算和对数运算等基本运算。这些运算可以用来处理各种加密算法、编码和纠错等应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值