RSA.docx

上传人:b****5 文档编号:7272028 上传时间:2023-01-22 格式:DOCX 页数:40 大小:473.98KB
下载 相关 举报
RSA.docx_第1页
第1页 / 共40页
RSA.docx_第2页
第2页 / 共40页
RSA.docx_第3页
第3页 / 共40页
RSA.docx_第4页
第4页 / 共40页
RSA.docx_第5页
第5页 / 共40页
点击查看更多>>
下载资源
资源描述

RSA.docx

《RSA.docx》由会员分享,可在线阅读,更多相关《RSA.docx(40页珍藏版)》请在冰豆网上搜索。

RSA.docx

RSA

[三、RSA的快速实现]

华中科技大学密码学课程设计报告

专业班级:

[信息安全0903]

学生姓名:

[曹晨业]

指导教师:

[崔国华]

完成时间:

2018年9月9日

RSA算法简介:

RSA算法基于一个十分简单的数论事实:

将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

RSA公开密钥密码体制。

所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制。

  在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。

加密算法E和解密算法D也都是公开的。

虽然秘密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK。

正是基于这种理论,1978年出现了著名的RSA算法,它通常是先生成一对RSA密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册。

为提高保密强度,RSA密钥至少为500位长,一般推荐使用1024位。

这就使加密的计算量很大。

为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES或IDEA对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。

对方收到信息后,用不同的密钥解密并可核对信息摘要。

  RSA算法是第一个能同时用于加密和数字签名的算法,也易于理解和操作。

RSA是被研究得最广泛的公钥算法,从提出到现在的三十多年里,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一。

RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价。

即RSA的重大缺陷是无法从理论上把握它的保密性能如何,而且密码学界多数人士倾向于因子分解不是NPC问题。

RSA的缺点主要有:

A)产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。

B)分组长度太大,为保证安全性,n至少也要600bits以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级;且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。

目前,SET(SecureElectronicTransaction)协议中要求CA采用2048bits长的密钥,其他实体使用1024比特的密钥。

C)RSA密钥长度随着保密级别提高,增加很快。

实验目的:

通过实际编程加深对RSA算法加密、脱密和密钥产生过程的理解。

实验原理:

1.大数存储和四则运算

根据RSA算法的要求,为了实现大数的各种复杂运算,需要首先实现大数存储和基本四则运算的功能。

当今开源的大数运算C++类有很多,多用于数学分析、天文计算等,本文选用了一个流行的大数类型,并针对RSA算法和本项目的具体需要对其进行了扩充和改进。

下面简单介绍大数存储和四则运算的实现原理。

最先完成的功能是大数的存储,存储功能由flex_unit类提供。

和普通的类型一样,每一个大数对应一个flex_unit的实例。

类flex_unit中,用一个无符号整数指针unsigned*a指向一块内存空间的首地址,这块内存空间用来存储一个大数,所以可以说,大数是被存储在一个以unsigned为单元的线性组中。

在方法voidreserve(unsignedx)中通过C++的new来给a开辟空间,当flex_unit的实例中被存入比当前存储的数更大的数时,就会调用reserve来增加存储空间,但是当flex_unit的实例中被存入比当前存储的数更小的数时,存储空间并不会自动紧缩,这是为了在运算的时候提高执行效率。

结合指针a,有两个重要的无符号整数来控制存储,unsignedz和unsignedn,z是被分配空间的单元数,随数字变大不断增大,不会自己紧缩,而n是当前存储的大数所占的单元数,组成一个大数的各unsigned单元的存入和读出由set、get方法完成,变量n是只读的。

类型unsigned在32位机是32位的,所以对于flex_unit这个大数类来说,每个大数最大可以达到个字节长,这已经超过了32位机通常的最大内存容量,所以是足够进行RSA所需要的各种运算的。

图3-2形象的说明了大数存储类flex_unit对大数的管理。

图3-2flex_unit对大数的管理

在flex_unit的存储功能基础上,将其派生,得到vlong_value,在vlong_value中实现四则运算函数,并实现强制转换运算符unsigned,以方便大数类型和普通整数的互相赋值。

当大数被强制转换为unsigned时,将取其最低四字节的值。

四则运算实现的原理十分简单,都是按最基本的算术原理实现的,四则运算过程的本质就是按一定数制对数字的计算,比如相加,就是低位单元对齐,逐单元相加并进位,减法同理。

而乘除法和取余也都是按照竖式运算的原理实现,并进行了必要的优化。

虽然实现了四则运算函数,但是若是程序里的运算都要调用函数,显得烦琐而且看起来不美观,所以我们另写一个类vlong,关联(Associate,即使用vlong_value类型的对象或其指针作为成员)vlong_value,在vlong重载运算符。

这样,当我们操作vlong大数对象的时候,就可以像使用一个简单类型一样使用各种运算符号了。

之所以将vlong_value的指针作为成员而不是直接构造的对象,也是为了提高执行效率,因为大型对象的拷贝要消耗不少机器时间。

2.大数幂模与乘模运算Montgomery幂模算法

在实现了vlong类型后,大数的存储和四则运算的功能都完成了。

考虑到RSA算法需要进行幂模运算,需要准备实现这些运算的方法。

所以写一个vlong的友元,完成幂模运算功能。

幂模运算是RSA算法中比重最大的计算,最直接地决定了RSA算法的性能,针对快速幂模运算这一课题,西方现代数学家提出了很多的解决方案。

经查阅相关数学著作,发现通常都是依据乘模的性质

,先将幂模运算化简为乘模运算。

通常的分解习惯是指数不断的对半分,如果指数是奇数,就先减去一变成偶数,然后再对半分,例如求D=

,E=15,可分解为如下6个乘模运算。

归纳分析以上方法,对于任意指数E,可采用如图3-3的算法流程计算。

图3-3幂模运算分解为乘模运算的一种流程

按照上述流程,列举两个简单的幂模运算实例来形象的说明这种方法。

①求

的值

开始D=1P=2mod17=2E=15

E奇数D=DPmodn=2P=PPmodn=4E=(E-1)/2=7

E奇数D=DPmodn=8P=PPmodn=16E=(E-1)/2=3

E奇数D=DPmodn=9P=PPmodn=1E=(E-1)/2=1

E奇数D=DPmodn=9P=PPmodn=1E=(E-1)/2=0

最终D=9即为所求。

②求

的值

开始D=1P=2mod17=2E=8

E偶数D=1P=PPmodn=4E=E/2=4

E偶数D=1P=PPmodn=3E=E/2=2

E偶数D=1P=PPmodn=9E=E/2=1

E奇数D=DPmodn=9P=不需要计算E=(E-1)/2=0

最终D=9即为所求。

观察上述算法,发现E根据奇偶除以二或减一除以二实际就是二进制的移位操作,所以要知道需要如何乘模变量,并不需要反复对E进行除以二或减一除以二的操作,只需要验证E的二进制各位是0还是1就可以了。

同样是计算

,下面给出从右到左扫描二进制位进行的幂模算法描述,设中间变量D,P,E的二进制各位下标从左到右为u,u-1,u-2,…,0。

Powmod(C,E,n)

{

D=1;

P=Cmodn;

fori=0toudo

{

if(Ei=1)D=D*P(modn);

  P=P*P(modn);

  }

  returnD;

}

选择与模数n互素的基数R=2k,n满足2k-1≤n<2k,n应为奇数。

并且选择R-1及n’,满足0

对于0≤m

M(m)

{

if(t≥n)return(t-n);

elsereturnt;

}

因为

,故t为整数;同时

,得

由于

,M(m)中t结果范围是0≤t<2n,返回时如果t不小于n,应返回t-n。

本软件程序中,RSA核心运算使用的乘模算法就是M(A*B)。

虽然M(A*B)并不是乘模所需要的真正结果,但只要在幂模算法中进行相应的修改,就可以调用这个乘模算法进行计算了。

本软件起初未使用Montgomery乘模算法时,加密速度比使用Montgomery乘模算法慢,但速度相差不到一个数量级。

将上述乘模算法结合前面叙述的幂模算法,构成标准Montgomery幂模算法,即本软件所使用的流程,叙述如下。

M(m)

{

k=(m*n’)modR;

x=(m+k*n)/R;

if(x>=n)x-=n;

returnx;

}

exp(C,E,n)

{

      D=R-n;

      P=C*Rmodn;

i=0;

      while(true)

         {

             if(E的当前二进制位Ei==1)D=M(D*P);//从低位到高位检测二进制位

i+=1;

if(i==E的二进制位数)break;

           P=M(P*P);

        }

  returnD*R-1(modn);

}

在具体的实现中,对应monty类的mul和exp方法。

全局函数modexp初始化monty对象并调用其exp方法,使用的时候直接调用modexp即可。

3.寻找素数Eratosthenes筛选与Fermat素数测试

首先在需要寻找素数的整数范围内对整数进行筛选,把所有确知为合数的整数排除出去。

程序中构造了一个数组b[],大小为一轮素数搜索的范围,记搜索范围大小为SS。

b[0]到b[SS]分别对应大数start到start+SS。

b[]中所有元素先初始化为1,如果对应的大数确定为合数,就将b[]中对应的元素置为0。

最后,只需对那些b[]中为1的元素对应的大数进行比较确切的素数测试即可,只要被测试的数是素数概率达到一定门限,就判这个数为素数。

这样做既保证了这段程序可以在短时间内执行完,又保证了可以以比较高的准确度得到素数。

函数find_prime先把b[]的所有元素赋值为1,然后按参数start给标记数组b[]的各元素赋0值。

下面描述标记数组b[]的赋0值算法。

首先,在类Prime_factory_san被构造的时候,构造函数中从2开始搜寻一些小素数,记录在数组pl[]中,共记录NP个。

这些小素数用来当作因子,他们的倍数将被从大素数搜索范围内剔除(即把数组b[]的对应元素标记为0),剔除的程序代码如下。

for(i=0;i

{

unsignedp=pl[i];

unsignedr=start%vlong(p);

if(r)r=p-r;

while(r

{

b[r]=0;

r+=p;

}

}

这里利用start对各小素数因子p求模的办法,得到当前p在素数搜索范围内的最小倍数在b[]中的对应位置,将其剔除后,不断后移p个位置,将这个小素数因子p在搜索范围内的所有倍数全部剔除,如图3-4所示。

在完成对所有小素数因子的类似操作后,他们的倍数在搜索范围内的位置标记b[r]被全部标记为0。

实际上这就是Eratosthenes筛选法。

图3-4在素数搜索范围内剔除小素数因子p的倍数

接下来,对可能为素数的数(即标记数组b[]中值为1的元素对应的数)进行素数测试。

取一个与p互素的整数A,对于大素数p来说应该满足Ap-1modp=1,但是我们把p代入一个大整数,满足这个关系的数不一定是素数。

这时我们改变A,进行多次测试,如果多次测试都通过,这个数是素数的概率就比较大。

按这种原理,我们编写素数测试函数如下。

intis_probable_prime_san(constvlong&p)

{

constrep=4;//测试次数

constunsignedany[rep]={2,3,5,7};//测试用的底数

for(unsignedi=0;i

if(modexp(any[i],p-vlong

(1),p)!

=vlong

(1))return0;

//modexp是幂模函数,按上一小节叙述的算法编码。

//这里modexp计算any[i]p-1modp。

return1;

}

测试通过,程序就判定这个数为找到的素数,将找到的素数返回给上层程序使用。

在这里其实有一个不可忽视的问题,就是得到一个测试通过的合数。

对于这种情况,RSA算法加密解密是否还可以实现,是一个需要从数学角度论证的问题。

因为得到素数的概率很高,经过一整天的生成密钥和加密操作,没有发现失败的密钥,所以本文暂没有对这个问题进行讨论。

综上所述,总结素数寻找的流程,如图3-5所示。

图3-5函数find_prime寻找素数的流程框图

得到了大素数,即RSA算法中的p、q,我们就可以计算出密钥,进行加密等操作了。

4.按常规RSA算法实现加密与解密

最后,类RSA_san基于前面的准备工作,实现RSA密钥生成和加解密的功能为了方便阅读,整个类的源程序中,所使用的变量字母均和RSA算法协议中一致。

在类RSA_san的构造函数里,执行准备一对随机密钥的操作。

之后可以直接使用类的其他成员进行RSA加解密操作,也可以载入以前保存的密钥或再次随机生成密钥。

类中各成员频繁的用到字符串和vlong类型的转换,因为大数是用字符串置入的,而把大数读出,也是保存在字符指针指向的一段内存空间里,所以也是字符串。

所以,需要实现一系列的编码转换函数,比如将unsigned指针指向的一段空间里保存的一个大数,表示成十六进制形式的字符串文本。

编码转换通常是用C风格的指针操作和sprintf函数来完成。

需要加密和解密的数据也是通过字符串参数置入的。

由于字符串的结尾字符“\0”实际上也可能是需要加密的数据,所以置入的串长度并不能以“\0”来决定,程序里引入一个unsigned类型的参数来决定置入的串长度,这样就解决了加密连0数据时候被截断的问题。

因为是对文件加密的软件,需要加密的数据通常并不止几字节,这时由上层程序将数据按用户的设置分块,分别加密或解密。

本软件默认的分块大小是1字节,即逐个字节作为参数,调用C++核心模块中的方法。

5、Rabin-Miller素数测试:

Rabin-Miller算法是典型的验证一个数字是否为素数的方法。

判断素数的方法是Rabin-Miller概率测试,那么他具体的流程是什么呢。

假设我们要判断n是不是素数,首先我们必须保证n是个奇数,那么我们就可以把n表示为n=(2^r)*s+1,注意s也必须是一个奇数。

然后我们就要选择一个随机的整数a(1<=a<=n-1),接下来我们就是要判断a^s=1(modn)或a^((2^j)*s)=-1(modn)(0<=j如果任意一式成立,我们就说n通过了测试,但是有可能不是素数也能通过测试。

所以我们通常要做多次这样的测试,以确保我们得到的是一个素数。

(DDS的标准是要经过50次测试)

采用Rabin-Miller算法进行验算

首先选择一个代测的随机数p,计算b,b是2整除p-1的次数。

然后计算m,使得n=1+(2^b)m。

(1)选择一个小于p的随机数a。

(2)设j=0且z=a^mmodp

(3)如果z=1或z=p-1,那麽p通过测试,可能使素数

(4)如果j>0且z=1,那麽p不是素数

(5)设j=j+1。

如果j且z<>p-1,设z=z^2modp,然后回到(4)。

如果z=p-1,那麽p通过测试,可能为素数。

(6)如果j=b且z<>p-1,不是素数

附代码:

#include

#include

#include

//随机数产生器

//产生m~n之间的一个随机数

unsignedlongrandom(unsignedlongm,unsignedlongn)

{

srand((unsignedlong)time(NULL));

return(unsignedlong)(m+rand()%n);

}

//模幂函数

//返回X^YmodN

longPowMod(longx,longy,longn)

{

longs,t,u;

s=1;t=x;u=y;

while(u){

if(u&1)s=(s*t)%n;

u>>=1;

t=(t*t)%n;

}

returns;

}

 

//Rabin-Miller素数测试,通过测试返回1,否则返回0。

//n是待测素数。

//注意:

通过测试并不一定就是素数,非素数通过测试的概率是1/4

intRabinMillerKnl(unsignedlongn)

{

unsignedlongb,m,j,v,i;

//先计算出m、j,使得n-1=m*2^j,其中m是正奇数,j是非负整数

m=n-1;

j=0;

while(!

(m&1))

{

++j;

m>>=1;

}

//随机取一个b,2<=b

b=random(2,m);

//计算v=b^mmodn

v=PowMod(b,m,n);

//如果v==1,通过测试

if(v==1)

{

return1;

}

i=1;

//如果v=n-1,通过测试

while(v!

=n-1)

{

//如果i==l,非素数,结束

if(i==j)

{

return0;

}

//v=v^2modn,i=i+1

v=PowMod(v,2,n);

i++;

}

return1;

}

Intmain()

{

unsignedlongp;

intcount=0;

cout<<"请输入一个数字"<

cin>>p;

for(inttemp=0;temp<5;temp++)

{

if(RabinMillerKnl(p))

{

count++;

}

else

break;

}

if(count==5)

cout<<"一共通过5次测试,是素数!

"<

else

cout<<"不是素数"<

return0;

}

6、Stein法求最大公约数

欧几里德算法是计算两个数最大公约数的传统算法,他无论从理论还是从效率上都是很好的。

但是他有一个致命的缺陷,这个缺陷只有在大素数时才会显现出来。

考虑现在的硬件平台,一般整数最多也就是64位,对于这样的整数,计算两个数之间的模是很简单的。

对于字长为32位的平台,计算两个不超过32位的整数的模,只需要一个指令周期,而计算64位以下的整数模,也不过几个周期而已。

但是对于更大的素数,这样的计算过程就不得不由用户来设计,为了计算两个超过64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法,这个过程不但复杂,而且消耗了很多CPU时间。

对于现代密码算法,要求计算128位以上的素数的情况比比皆是,设计这样的程序迫切希望能够抛弃除法和取模。

Stein算法由J.Stein1961年提出,这个方法也是计算两个数的最大公约数。

和欧几里德算法算法不同的是,Stein算法只有整数的移位和加减法,这对于程序设计者是一个福音。

为了说明Stein算法的正确性,首先必须注意到以下结论:

∙gcd(a,a)=a,也就是一个数和他自身的公约数是其自身

∙gcd(ka,kb)=kgcd(a,b),也就是最大公约数运算和倍乘运算可以交换,特殊的,当k=2时,说明两个偶数的最大公约数必然能被2整除

有了上述规律就可以给出Stein算法如下:

1如果A=0,B是最大公约数,算法结束

2如果B=0,A是最大公约数,算法结束

3设置A1=A、B1=B和C1=1

4如果An和Bn都是偶数,则An+1=An/2,Bn+1=Bn/2,Cn+1=Cn*2(注意,乘2只要把整数左移一位即可,除2只要把整数右移一位即可)

5如果An是偶数,Bn不是偶数,则An+1=An/2,Bn+1=Bn,Cn+1=Cn(很显然啦,2不是奇数的约数)

6如果Bn是偶数,An不是偶数,则Bn+1=Bn/2,An+1=An,Cn+1=Cn(很显然啦,2不是奇数的约数)

7如果An和Bn都不是偶数,则An+1=|An-Bn|,Bn+1=min(An,Bn),Cn+1=Cn

8n++,转4

这个算法的原理很显然,所以就不再证明了。

现在考察一下该算法和欧几里德方法效率上的差别。

考虑欧几里德算法,最恶劣的情况是,每次迭代a=2b-1,这样,迭代后,r=b-1。

如果a小于2N,这样大约需要4N次迭代。

而考虑Stein算法,每次迭代后,显然AN+1BN+1≤ANBN/2,最大迭代次数也不超过4N次。

也就是说,迭代次数几乎是相等的。

但是,需要注意的是,对于大素数,试商法将使每次迭代都更复杂,因此对于大素数Stein将更有优势。

7、拉宾-米勒测试:

这是一个相当快速的随机算法(有较小的可能性错误),用于判断一个大数是否是素数。

快速素数检验是目前大部分公钥密码体系的关键。

  米勒-拉宾检验的内容是:

要测试N是否为质数,首先将N-1分解为2^sd。

在每次测试开始时,先随机选一个介于[1,n-1的整数a,之后如果对所有的rin[0,s-1],若a^dmodN<>1且a^{2^{rd}}modN<>-1,则N是合数。

否则,N有3/4的机率为质数。

8、Montgomery快速幂模算法:

附代码:

viewplaincopytoclipboardprint?

unsignedintpower(unsignedintn,unsignedintp)

{

//计算n的p次方

unsignedinttmp=1;

while(p>1)

{

/

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 其它

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1