大整数基本运算研究报告与实现分析.docx
《大整数基本运算研究报告与实现分析.docx》由会员分享,可在线阅读,更多相关《大整数基本运算研究报告与实现分析.docx(27页珍藏版)》请在冰豆网上搜索。
![大整数基本运算研究报告与实现分析.docx](https://file1.bdocx.com/fileroot1/2022-11/22/f0771682-5d50-4d2f-aa65-5d26b9a5f940/f0771682-5d50-4d2f-aa65-5d26b9a5f9401.gif)
大整数基本运算研究报告与实现分析
大整数乘法的实现与分析
摘要
随着计算机信息安全要求的不断提高,密码学被大量应用到生活中。
RSA、ElGamal、DSA、ECC等公钥密码算法和数字签名算法都建立在大整数运算的基础上,比较耗时的大整数乘法、除法、模乘、幂运算、幂乘等运算却被上述算法大量使用,它们的运算速度对这些算法的高效实现起着重要的作用,如何快速实现上述几种运算是公钥密码领域普遍关注的热点问题。
本文基于32位的系统,首先采用模块化的思想建立大整数运算库的基础框架,在实现一些辅助函数后在此框架上讨论并实现多精度大整数的基本加法、减法、乘法、除法、平方算法、缩减、模乘、模幂乘等算法。
所用程序均采用C/C++语言编写,所采用的优化也均建立在C/C++语言这一层面上,在保证算法有足够高的效率的同时力求代码清晰易懂,函数接口简单明了,具有可移植性和稳定性。
关键词:
多精度大整数,Comba,Montgomery,二分查找,笔算
注:
本设计<论文)题目来源于企业工程。
Abstract
Nowadays,ascomputerinformationsecurityrequirementsimprovecontinuously,thecryptologyhasbeenwidelyappliedtolife.PublickeycryptographicalgorithmsanddigitalsignaturealgorithmssuchasRSA,ElGamal,DSA,ECCareallbaseonmultipleprecisionarithmetic.Multipleprecisionmultiplication,Division,modularmultiplication,exponen-tiation,modularexponentiationwhichneedmoreworkingtimeisusedbypublickeycryptographicalgorithmswidely,theirspeedisveryimportanttotheimplementationsofthosealgorithms.Howtofastimplementthosearithmeticaboveisthehottopicinthepublickeycryptographicfield.
Thispaperisbasedonthe32bitsystem.Firstofall,wefoundthemodularfoundationofmultipleprecisionarithmeticlibrary。
Aftersomeauxiliaryfunctionisformed,wediscussandimplementthemultipleprecisionintegerbasicaddition,Subtraction,multiplication,,kindsofsquarealgorithms,division,reduction,andsomerelationalfunction.AllthealgorithmdiscussinthispaperisimplemententirelyinportableISOC/C++andtheoptimizationofthosealgorithmsimplementationsisbuiltontheC/C++languagelevel.thealgorithmhashighenoughtoensuretheefficiencyofthecodeatthesametimestrivetoclearlyunderstand,simpleinterfacefunctionwithportabilityandstability.
Keywords:
MultiplePrecisionInteger,Comba,Montgomery,Binarysearch,
Writtencalculation
1绪论
1.1题目的背景
科学技术和网络的发展使计算机深入到了各行业的方方面面,计算机在带来方便和提高了工作效率的同时却也带来了各种各样的新问题,其中信息安全问题最为突出,随着计算机信息安全要求的不断提高,计算机保密系统已变得越来越重要。
随着香农定理的发表,信息安全技术得到了迅猛的发展。
密码学应用不再是局限于军事、国防等有限领域,而是迅速的走进了千家万户。
RSA、ElGamal、DSA、ECC等公钥密码算法和数字签名等算法得到了快速发展。
其实现都是建立在大整数运算的基础上,耗时的大整数乘法、除法、模乘、幂运算、模幂乘等运算更是被上述算法大量使用。
而计算机微机的字长限制对信息安全中大整数的操作,带来了巨大的困难。
它们的运算速度对这些算法的高效实现起着重要的作用,如何快速实现上述几种运算是公钥密码领域普遍关注的热点问题。
1.2国内外研究状况
长期以来,各方面的工作者对大数基本运算的快速实现问题进行了大量研究,主要围绕模幂算法设计与优化、模乘算法设计与优化、专用芯片设计等,并且已经取得不少研究成果。
模幂通常都转化为一系列模乘和模平方运算,目前较好的算法已经能够将1次n比特数的模幂运算转化为约5n/4次n比特的模乘运算,再减少模乘的次数的难度很大,因此,提高模乘的速度是模幂快速实现的关键[1]。
目前,模乘主要有估商型、加法型和Montgomery型3类方法。
1960年,Pope和Stein就提出了采用估商方法将模乘转化为一系列乘法和除法进行计算的思想;1981年,Knuth具体给出了一种转换为乘法和除法的估商型模乘算法[2];1987年,Barrett提出了一种转换为乘法和加法而没有除法的估商型模乘算法[3]。
1983年,Blakley提出了一种加法型模乘算法,其设计思想是将模乘转换为一系列加法。
为减少加法次数,人们提出了窗口模乘算法和滑动窗口模乘算法,并相继提出不少改进方法,获得较理想的结果。
1985年Montgomery提出了一种计算模乘的有效算法,其设计思想是将普通模乘转换为易于计算的特殊模乘[4]。
此后,人们提出了不少基于Montgomery思想的改进算法,并得到了广泛的实际应用。
大多数情况下,一种算法的理论描述和实际实现之间有不小的差距,是两个完全的不同的概念,因此,众多学者为这些优秀算法的具体的软、硬件实现、优化付出了辛勤的汗水,在软件实现方面这些算法多数被包含在某些算法库中,其中也有不少是开放源代码的算法函数库,最著名的就是GNU的号称地球上最快的多精度大数库GMP,还有Miracl、openssl、cryptopp、cryptlib、flint等优秀的算法库,而我国的郭先强先生的HugeCalc.dll库也同样出名,虽然它不是开放源码的,但它的速度赶得上GMP甚至在某些方面超越了GMP。
然而,正如TomStDenis所说,不存在一个易懂的大数库!
从目前收集到的信息看,凡是效率高的算法实现,要么结构复杂、层次庞大,要么编码风格奇特;所有速度快的库都使用了汇编,同时大部分都使用了MMX、SSE2、SIMD系列指令作加速,也对多核心CPU进行了优化,使用了多线程等,甚至运行时监测CPU类型而使用相关的特殊优化,最大限度地榨取CPU的性能。
然而,这些很好的优化技术却大大地降低了代码的可读性,极大地提高了理解、学习的门槛。
在学术专著方面,大数算术也备受欢迎,DonaldE.Knuth也用了一整本书——《TheArtofComputerProgrammingVolume2》来从理论的角度讲述了多精度算术,并讲解了高效的算法背后关键的数学概念;《HandbookofAppliedCryptography》[6]也讲述了应用密码学相关的大数算术算法的有效实现;而《KryptographieinCundC++》[7]和《BigNumMath:
ImplementingCryptographicMultiplePrecisionArithmetic》等则从编码学的角度对大数算术进行了多方面的讨论。
1.3本文研究内容
本文基于32位的系统,首先采用模块化的思想建立大整数运算库的基础框架,在实现一些辅助函数后在此运算库框架上讨论并实现多精度大整数的基本加法、减法、乘法、平方算法、缩减算法、模乘、模幂乘等算法。
本文讨论的所用程序均采用C/C++语言编写,所采用的优化也均建立在C/C++语言这一层面上,在保证算法有足够高的效率的同时力求代码清晰易懂,函数接口简单明了,具有可移植性和稳定性。
2大整数的结构
大整数运算是一些相当复杂的运算,本文要实现的是大整数基本运算,采用模块化思想按照层次结构来设计及实现一些其它的辅助函数,而不是把它们内嵌在算法函数内,这样既能够避免算法函数的程序代码的过分冗长,使代码清晰易懂、突出算法过程又能够使程序易于测试、排错、更新和复用。
由于本文是介绍大整数的基本算法,在本文开始之前只介绍一些关键的辅助函数,其它辅助函数是在相关算法实现的时候再简略介绍它的功能。
2.1大整数的存取结构
2.1.1大整数结构分析
大数的存储方式主要是有两种——链式存储方式和顺序存储方式。
一是采用链表作为存储结构,这种方式可以适应不定长度的大数,这种方式的存储空间包括整数表示部分和链表指针部分,其空间利用率不高,而且其存储空间是离散的,所以随机访问效率也不高,而且频繁的堆操作和引用操作势必大量增加开销,不利于编译器优化速度;另外,根据内存管理方式的不同,顺序存储方式可以再分为静态存储方式和动态存储方式。
静态存储方式数组的大小是事先已经知道的,适合知道大小的整数运算。
而因其数组长度是固定不变的,在运算的时候容易造成溢出。
所以其不适合不定长度的整数运算。
而动态方式,其可以动态分配内存空间。
可以根据整数位数的变化调整大小。
其是最常用的顺序存储方式,而且顺序存储方式是连续分配空间,所以其可以实现随机访问,提高速度,因为存储空间就是整数本身,没有其他额为开销,所以空间利用率也较高。
由于受到计算机字长的限制制约着大整数的运算,所以必须要解决大整数表示问题,通常是采用B进制表示,如
,其表示为:
<
),而且系数
<
)必须是计算机可以表示的常数。
在基选择上,最容易想到也是最直接易懂的是用整数数组来保存大数,数组的每个元素保存大数的一个十进制数字,这种方式操作比较简单,但这种方式需要较多的额外运算,而且效率明显不高,也需要比较多的存储空间;效率比较高的,被采用的比较多的方式是用B进制数组存储大数,即把大数转化B进制表示,数组的每个元素存储这个B进制数的一个B进制的数位,这样既方便计算机处理又提高了内存的利用率,同时缩短了大数的实际表示长度,减少了循环的次数,有利于提高效率。
为了更好的适应各种算法的需要及避免过度浪费存储空间,本文采用多精度的方式。
1985年,西安交通大学的王永祥副教授曾经采用过万进制的方法表示大数,并实现了自己的大数算法[11]。
根据万进制的原理,本文决定将整数进制B取为2的某次幂。
本文是基于32位系统,VC++有__int64这样的64位整数类型,但32位硬件上毕竟不能直接处理64位整数,那势必需要附加内部操作来完成。
为了匹配硬件操作,取B进制为半个CPU字长的unsignedshortint型,即216进制.由于m位的数乘以n位的数最多将产生m+n位的数,所以两个216进制数位的乘积可以用一个232数来保存而不超出CPU的字长。
2.1.2大整数结构
为了模块化编程,程序结构清晰易懂。
本文在开始之前先对大整数做一个结构封装,使用一个结构体来表示大整数。
标示符MBigInt表示一个多精度的大整数结构:
typedefstruct/*定义大整数的结构*/
{
longintalloclen。
/*记录数组已经分配总的空间大小*/
longintlength。
/*记录大整数的长度,即系数的个数*/
intsign。
/*记录大整数的符号*/
un_short*pBigInt。
/*指向大整数的数据的指针,即指向系数的地址*/
}MBigInt。
上面大整数封装的结构中,分别记录大整数分配到的空间alloclen大小和已经在使用到的空间length大小。
使用这种结构能够有效地管理内存,减少堆操作的次数,减少相关操作带来的性能损失;通过一个指针来指向保存大整数的数据的数组,这样有利于内存的动态调整,可以不移动数组的任何元素而交换两个大整数,其只需要交换三个内置的整数值和一个指针就可以了。
本文所讨论的大整数的数位是按照低位在前的方式存放,则按从低位到高位的顺序把大整数的数位按下标从小到大顺序存放到数组中去,也就是十进制表示方式相反方向,这样既有利于扩展大数的长度也有利于缩减。
2.2预定义的变量
因为在编程的时候很多的变量字符比较长,很容易略写某个字符,造成编程错误,所以本文首先对一些变量进行预定义。
typedefunsignedshortintun_short。
/*16位数的声明符号*/
ypedefunsignedlongintun_long。
/*32位数的声明符号*/
#defineBIT_PRE_WORD16UL/*每个单精度数字含有的bit数*/
/*定义信号标识*/
RETURN_OK_BINT1/*正常返回*/
RETURN_FAILE_BINT0/*错误返回*/
INITIAL_BINT49/*默认分配的大小*/
STEP_BIINT16/*起跳值*/
对于大整数的符号,本文只区分正数与负数,若大整数的sign分量为1则表示该大整数是正数,这要求其它函数在运算到使sign分量为-1的保证设置该大整数为负。
用un_short表示大整数的一位数字,一下简称单字或字,两倍于un_short大小的则称为双字,三倍于un_short大小的则称为三字,由于双字用un_long来表示,已经达到了32位CPU的字长。
所以在需要三字的地方本程序通过模拟的方式达到相关效果,详细见后面介绍。
2.3大整数基本函数定义
因为在大整数的基本运算中,很多的操作都是类似和重复的,所以本文在开始介绍基本运算之前,先对这些基础函数进行说明。
2.3.1大整数初始化操作
函数initMBInt(MbigInt*bint,longintsize=49>的目的是初始化一个大整数,默认长度是49字节。
因为采用了动态分配内存的方式,所以大整数需要一些函数处理堆上的操作。
为了在整数基本运算中减少多次进行堆操作,本函数分配的内存大小有个初始值INITIAL_BINT,如果大整数的输入大小size小于该值,则都会分配该值大小的数位。
否则在起跳值的基础上以步长STEP_BIINT为增量进行递加直到大于size,然后分配该大小的数位。
成功分配后所有数据位都被置为0,符号标记为非负。
在本实现中,初始值INITIAL_BINT在头文件中被定义为48+1即每个大整数的最小长度为48*16+1*16bit,而步长STEP_BINT在头文件中被定义为16即256bit,因为512bit的公钥算法已经不安全,所以本程序从768bit起跳,要多加16bit是因为很多公钥算法的长度都是512的倍数,如果大整数的长度刚好等于公钥算法的长度则很多时候会引起不必要的扩充内存的操作,所以本程序加了16bit的零头。
2.3.2大整数的销毁操作
函数deleteMBInt(MbigInt*bint>用于释放大整数所得的堆内存,并将符号标记为非负,要再次使用该数则必须先重新初始化;在较复杂的函数中,若某一步<例如调用子函数)执行失败,则需要调用deleteMBInt函数释放该一步之前初始化的所有的大整数以免做成内存泄漏。
2.3.3大整数的扩展
扩展是指在运算操作的时候,结果超出了现在大整数的大小就追加空间,使得大整数能够得到足够空间来存储数据。
extendMBInt(MBigInt*bi,longintsize>以STEP_BINT为增量在原来的大小上递加直到大于size,然后分配该大小的数位保存数据。
2.3.4大整数的输入和输出函数
因为我们最为熟悉的数字和使用的最多的也是十进制表示形式,所以本文对大整数的输入和输出都是使用十进制形式。
函数read_radix(MBigInt*bi,char*str>是将十进制表示的字符串读入并转换为大整数结构表示;函数write_radix(MBigInt*bi,char*str>将大整数转换为十进制的字符串表示。
当然可以通过对以上两个函数的修改,形成不同的B进制输入和输出方式。
2.4大整数的移位函数
2.4.1字移位运算
字移位是指对大整数左移或右移动16个比特位,即大整数数组的一个元素的大小。
从大整数的结构可以知道,将大整数转换成了B进制数数组,此时可以将大整数看成是B的多项式,则大整数a可以很方便地看成
,那么
或者
的运算就可以通过将数组的元素向左或向右移动b个B进制数位的方式快速地完成,算法复杂度仅为O(n>,速度大大得提高了。
函数Left_shift_word(bigIntMP*dst,longintn>用于将大整数dst左移n个字即乘以Bn;而函数Right_shift_word(bigIntMP*dst,longintn>则用于将dst右移n个字,这相当于除以Bn。
1、字左移运算
字左移运算就是对整数进行一次乘法运算,乘数是基B的n次方。
可以通过简单的节点左移得到。
//左移n个字
intLeft_shift_word(MBigInt*dst,longintn>
{//为目标数组分配足够多空间
if(dst->length+n>dst->alloclen>
{
intresult=0。
if((result=extendMBInt(dst,dst->length+n>!
=RETURN_OK_BINT>>
returnresult。
}
for(inti=dst->length-1。
i>=0。
i-->//左移n个字
{
dst->pBigInt[i+n]=dst->pBigInt[i]。
}
for(i=0。
ii++>//左移后对前面n个字清0
dst->pBigInt[i]=0。
dst->length+=n。
//大整数长度的设置
returnRETURN_OK_BINT。
}
运算结果如下:
图2.1左移字运算结果
2、字右移运算,其实就是对整数进行一次除法运算。
除数是基B的n次方。
可以通过简单的节点左移得到。
但其仅是计算商,而不保存余数。
//右移n个字
intRight_shift_word(MBigInt*dst,longintn>
{
intlen=dst->length。
//大整数的长度
for(inti=0。
ilength。
i++>//右移n个字
{
dst->pBigInt[i]=dst->pBigInt[i+n]。
}
for(i。
ii++>//对高n个字清0
{
dst->pBigInt[i]=0。
}
dst->length-=n。
//重设置大整数长度
returnRETURN_OK_BINT。
}
运算结果如下:
图2.2右移字运算结果
2.4.2比特移位运算
比特移位是指对大整数进行一个比特的左移或右移。
根据大整数的结构定义,大整数的基是2的幂。
所以大整数乘以或除以2都可以通过简单的左移一位或右移一位得到结果。
左移一个比特就是对大整数进行乘以2操作,右移的时候就是对大整数进行除以2操作,其也仅是得到除以2的商,而没有保存余数。
1、大整数左移一个比特位
//左移一位运算
intLeft_shift_bit(MBigInt*dst,MBigInt*src>
{
longintlen=0。
//用来保存目的操作数的程度
longinti=0。
unsignedintcarry=0。
un_short*pDst=dst->pBigInt。
//指向目的大整数存取数组的指针
un_short*pSrc=src->pBigInt。
//指向源大整数存取数组的指针
if(dst->alloclenlength+1>//确保目的大整数能够容纳移位后的结果
{
intresult=0。
if((result=extendMBInt(dst,src->length+1>
!
=RETURN_OK_BINT>>/*扩大大整数空间,保证能够存储*/
{
returnresult。
}
pDst=dst->pBigInt。
}
//记录目的操作数原来的已用空间,方便后面处理
len=dst->length。
dst->length=src->length。
pSrc=src->pBigInt。
//源大整数数组元素分别左移一位,并赋给目的大整数
for(i=0。
ilength。
++i>
{
pDst[i]=(un_short>(carry=((pSrc[i]><<1>|(carry>>16>>。
}
if((carry>>16>!
=0>
{/*若最高数位左移后溢出,则将溢出的比特存到下一个字*/
pDst[src->length]=(un_short>(carry>>16>。
++(dst->length>。
}
for(i=dst->length。
i++i>/*清除可能残留的旧数据*/
pDst[i]=0。
dst->sign=src->sign。
//符号更改
returnRETURN_OK_BINT。
}
运算结果如下:
图2.3左移一比特运算结果
1、大整数右移一个比特位
//右移一位运算
intRight_shift_bit(MBigInt*dst,MBigInt*src>
{
longintoldlen=0。
//保存目的大整数的原来长度
longinti=0。
unsignedintcarry=0。
un_shorttemp=0。
un_short*pDst=dst->pBigInt。
//目的大整数数组指针
un_short*pSrc=src->pBigInt。
//源大整数数组指针
if(dst->alloclenlength>//确保目的大整数能够容纳移位后的结果
{
intresult=0。
//扩展目的大整数长度
if((result=extendMBInt(dst,src->leng