在GF(2^8)下可做任意两个数乘法的程序

2024-05-26 11:08
文章标签 程序 两个 任意 乘法 gf

本文主要是介绍在GF(2^8)下可做任意两个数乘法的程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作业要求:

写一个GF(2^8)下可做任意两个数乘法的程序。

GF(2^8),也就是在一个字节上所做的乘法和加法都封闭的一个有限域。

在GF(2^8)上只有两种运算:异或加运算和乘法运算。

其中异或加运算就是1 xor 1 = 0, 0 xor 0 = 0, 1 xor 0 = 1。(原谅我的啰嗦)

乘法运算的规则就是:

(1)A * 2的时候,A左移一位;A * 4 = (A * 2) * 2,A * 8 = ((A * 2) * 2) * 2,容易看出乘上2的n次幂是一个不断迭代的过程。

(2)那么A * 6的情况有如何?很简单,A * 6 = (A * 2) xor (A * 4),注意在该域中加法都是xor运算,而不是算术加运算。另外,可能有人会问:A * 4 = (A * 2) xor (A * 2) = 0,这不是和(1)冲突了吗?

原因在于:A * 6 = A * 00000110,其中00000110 = 00000100 xor 00000010,但是A * 4 = A * 00000100,00000100 != 00000010 xor 00000010 (= 00000000)。

(3)在乘法运算中,若乘数左移结果中第8位左边非0,例如10000000 * 00000100 = 10000000 * 2 * 2,其中10000000 * 2 = (1)00000000,结果溢出,所以要再和1BH进行异或加运算,即结果为00000000 xor 00011011 = 00011011。然后00011011 * 2 = 00110110即为结果。

错误做法:10000000 * 4 = (10)00000000 xor 00011011 = 00011011,为什么呢?因为10000000 * 2已经溢出,要先做溢出处理。

注:为什么是1BH?因为在AES的设计中开发者选用了不可约多项式m(x) = x^8 + x^4 + x^3 + x + 1,所以如果结果溢出就要再xor一个00011011(也就是1BH)。


在基本的运算法则搞懂以后,写程序就很简单了。

最近写OC写了很多,Java上学期也一直在写,反而C++好久没写了,突然又用回C++来写程序,还真的差点没转过弯来。所以现在趁着写博客的机会做下笔记。

好吧,回到程序来。

先来看看主函数,大致看看整个程序的流程:

int main()
{   int a[8], b[8], c[8], i=0;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 ch='y';while(ch=='y'||ch=='Y'){//  -- 清空之前的计算结果 --for(i=0; i<8; i++){c[i]=0; // 清空结果}state=0; // 清空当前左移位数//  -- 输入乘数a和b --if(input(a, 'a')==0) // 如果输入a没有出错{if(input(b, 'b')==0) // 如果输入b没有出错{//  -- 程序开始执行,开始计时 --QueryPerformanceCounter(&start_t);//  -- 执行乘法运算 --for(i=7; i>=0; i--){if(b[i]==1){xor(c, leftshift(a, 7-i)); // 求和}}//  -- 输出运算结果 --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:";ch=cin.get();if(cin.get()==10){// 跳过输入y/n后的回车键,防止影响下面的输入}}return 0;
}

结构非常清晰:清空之前的运算结果 —— 输入乘数a和b —— 执行乘法运算a * b —— 输出运算结果c以及计算时间  —— 是否循环执行。

首先来看看封装好的输入函数:

/* * 分别输入两个GF(8)内的乘数 * 参数temp[]用于保存输入的二进制数组* 参数name为变量名称,如a,b* 返回类型为输入后的状态,若为0则成功,若非0则出错*/
int input(int temp[], char name)
{char c;int i=0;cout<<"请按8位二进制格式输入乘数"<<name<<",以回车结束:";c=cin.get();while(c!=10) // 若输入的不是回车{switch(c){case '0':temp[i++]=0;break;case '1':temp[i++]=1;break;default:cout<<"程序出错:数字输入错误,不是0或1";cin.ignore((numeric_limits<streamsize>::max)(),'\n'); // 清除输入缓冲区中的所有内容return 1;}c=cin.get();if(c==10&&i!=8){cout<<"程序出错:输入的数位数不等于8";return 2;}}return 0;
}

注意输入的数由于要在GF(2^8)内,所以必须符合要求:8位,每一位都是0或1,例如00000010。

若输入错误可以直接exit程序,这里做成了回调错误状态到主函数并询问是否重新输入的循环形式。


在讲解执行a * b之前,先看看xor运算:

/* 对a和b进行异或求和运算 */
void xor(int a[], int b[])
{for(int i=0; i<8; i++){a[i]=bitxor(a[i], b[i]);}
}/* 按位进行异或求和运算 */
int bitxor(int a, int b)
{if(a==b){return 0;}else {return 1;}
}

xor()是对一个字节的a[8]和b[8]进行xor求和,结果保存到a[8]中。

bitxor()是逐位相与。


再看看求和语句:

//  -- 执行乘法运算 --
for(i=7; i>=0; i--)
{if(b[i]==1){xor(c, leftshift(a, 7-i)); // 求和}
}

看看关键的leftshift函数:

/* * 计算a[]乘上2的len次幂的结果, len为左移的位数* 参数temp[]用于保存移位后的数组,用于迭代进行下一次移位* 参数len为左移的位数,例如10000000的左移位数为7* state用于保存temp对应的左移位数* 返回结果为移位后的数组*/
int *leftshift(int temp[], int len)
{if(len==0){state=0;return temp;}// 由于temp保存了之前的位移结果,所以只需要左移len-state位for(state; state<len; state++){shift1(temp); // 乘2,或者说左移1位(包含溢出处理)}return temp;
}

为了说明该算法的思想, 首先举个例子:对于a * 00001100 = (a * 00001000) xor (a * 00000100),这时有两种计算方法(假设一次乘法或一次加法为1次计算):

(1)计算a * 00000100得结果a left shift 2,再计算a * 00001000得结果a leftshift 3,然后得a = (a left shift 2) xor (a left shift 3)。

计算次数为 2 + 3 + 1 + 溢出的加法次数x = 6 + x。

(2)在(1)的基础上,注意到a left shift 3 = (a left shift 2) * 2,所以我们可以先计算a left shift 2,然后使用a left shift 2的结果乘2(只需要1次乘法)得到结果a left shift 3,这样就避免了在计算a left shift 3时又要从a计算起。

计算次数为 2 + 1 + 1 + 溢出的加法次数y = 4 + y。

明显地,y <= x,也就是方法(2)的运算效率高于方法(1)。

在本程序中,对于a[8] * b[8],我们从b[7]计算到b[0],也就是a[8] * 2的次数从0一直计算到7,其中每次乘法都运用了之前乘法运算保存的结果,该结果由temp数组保存,state则保存了temp数组对应的移位次数,len为本次乘法原来需要乘2的次数(这里乘2和左移是一致的,在讲述时忽略溢出的处理情况)。其中state为全局静态变量,初始为0:

static state=0; // 用于记录左移的位数
还有左移1位(乘2)的函数shift1:

/* 乘2,左移1位,如果溢出则异或1BH */
void shift1(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);}
}


最后输出c,即运算结果,以及计算所用时间(由于要与后面博客中的查表乘法运算的效率相比较)。


还是Run一下吧,写博客的习惯:





好久没写C++,小小回顾一下:

1.若要在主函数中调用其它函数,必须将该函数放在主函数之前,或者提前做好函数声明,例如:

#include <iostream>
#include <limits> // numeric_limits
#include <windows.h>
using namespace std;
int input(int temp[], char name);
void xor(int a[], int b[]);
int bitxor(int a, int b);
int *leftshift(int temp[], int len);
void shift1(int a[]);


2.判断输入是否按了回车换行,可以通过以下方法:

c=cin.get();
while(c!=10)
{
}
其中10(0AH)对应的ACII码为换行。

其中语句cin.get()对应一次输入,而不是上一次的输入。


3.如果要强制退出程序,可以直接使用exit(1)退出,而不需要回调数据到main函数再return。


4.调用函数时数组可以直接作为参数传值:int(a[], b[])。


5.当函数的返回类型是数组时,可以首先在函数中声明一个数组指针:

int *shift=new int[8];

在写程序时没有声明长度,结果一直弹出Debug Assertion Failed!的错误。

在函数结束时返回该指针:

return shift;

注意shift必须new,否则返回的指针所指向的数组可能其值全部为负最大值。

函数声明:

int *leftshift(int a[], int len);


6.如果要清空输入缓冲区中的内容,可以使用以下方法:

cin.ignore(numeric_limits<streamsize>::max(),'\n'); // 清除输入缓冲区中的所有内容

注意必须导入头文件

#include <limits> // numeric_limits 


关于limits的max()和windows.h冲突的解决方法见numeric_limits::max()和windows.h冲突的解决方法


7.计算程序运行时间的方法:

(1)使用time.h给出的函数

首先导入头文件:

#include <time.h>


声明变量如下:

time_t start,end,time; // 计时所用的变量名称

开始计时:

//  -- 程序开始执行,开始计时 --
start=clock();


计时结束:

//  -- 程序结束执行,结束计时 --
end=clock();
time=end-start; // 这里的时间是计算机内部时间
cout<<endl<<"程序用时:"<<time<<endl;

(2)使用windows.h给出的函数

详见C/C++ 各种计时函数总结



这学期密码学关于编程的作业好像还挺多的,所以专门开个类别写些博客来记录,这篇博客可能还没涉及到密码安全学方面的问题,但随着课程的深入,肯定会有更多与这方面相关的内容(表示压力很大),我会继续写博客更新的。










这篇关于在GF(2^8)下可做任意两个数乘法的程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1004293

相关文章

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

hdu 6198 dfs枚举找规律+矩阵乘法

number number number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Problem Description We define a sequence  F : ⋅   F0=0,F1=1 ; ⋅   Fn=Fn

这些心智程序你安装了吗?

原文题目:《为什么聪明人也会做蠢事(四)》 心智程序 大脑有两个特征导致人类不够理性,一个是处理信息方面的缺陷,一个是心智程序出了问题。前者可以称为“认知吝啬鬼”,前几篇文章已经讨论了。本期主要讲心智程序这个方面。 心智程序这一概念由哈佛大学认知科学家大卫•帕金斯提出,指个体可以从记忆中提取出的规则、知识、程序和策略,以辅助我们决策判断和解决问题。如果把人脑比喻成计算机,那心智程序就是人脑的

uniapp设置微信小程序的交互反馈

链接:uni.showToast(OBJECT) | uni-app官网 (dcloud.net.cn) 设置操作成功的弹窗: title是我们弹窗提示的文字 showToast是我们在加载的时候进入就会弹出的提示。 2.设置失败的提示窗口和标签 icon:'error'是设置我们失败的logo 设置的文字上限是7个文字,如果需要设置的提示文字过长就需要设置icon并给

基于SpringBoot的宠物服务系统+uniapp小程序+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统+原生微信小程序+LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统+LW参考示例 3.基于SpringBoot+Vue的企业人事管理系统+LW参考示例 4.基于SSM的高校实验室管理系统+LW参考示例 5.基于SpringBoot的二手数码回收系统+原生微信小程序+LW参考示例 6.基于SSM的民宿预订管理系统+LW参考示例 7.基于