RSA应用现状及应用于文件加密的分析

Research And Development Of
RSA File Encryption

摘 要
分析RSA算法的应用现状,论证文件加密应用RSA算法的可行性和意义。设计一套完整实用的RSA文件加密解决方案,具体编码实现。对RSA算法进行研究,从常规RSA算法出发,用C++实现RSA加密算法类库,并在32位windows平台封装成组件。在.Net平台引用此组件,实现可以对任意文件进行RSA加密操作的窗体应用程序。经过加密的文件以及密钥文件都是文本文件。给出关键类类图、整个应用程序的结构描述文档、关键模块流程图、较详细的接口文档、所有源代码。对应用程序进行测试,对测试结果进行分析研究,进而对应用程序进行改进,对关键算法进行尽可能的优化,最终得到一个在windows运行的可以用指定密钥对任意文件进行RSA加密并可解密的完整应用程序,和一些相关的可移植组件。
关键词 RSA RSA算法 文件加密 加密成文本
Abstract
Do research about the application area of RSA encryption and reason that RSA can be used for file encryption. Design a RSA file-encrypt solution and complete an application on Microsoft Windows™. Design a C++ class based on normal RSA algorithm. And make a DLL module based on the class. Then complete a .Net Framework™ window-application using that DLL. The application can encrypt any file and decrypt them. The file after encryption can be saved as a text file. And the encryption-keys also can be saved as text.Provide pivotal classes chart, project description, core algorithm flowchart, all source code, and module interfaces document. Do application performance test and record the performance data. Analyze the result then optimize core algorithm and improve the application. Finally, create a practical application using RSA algorithm that can encrypt and decrypt any file. And several modules in the project can be reuse by other applications. For instance, the C++ class can be cross-compiled for handheld devices, the DLL can be referenced by other win32 applications, and the .Net class can be easily referenced by web server applications or web services.
Keywords RSA RSA algorithm file encryption encrypt to text

目 录
前 言 1
第1章 RSA应用现状及应用于文件加密的分析 2
1.1 RSA算法介绍与应用现状 2
1.2 RSA应用于文件加密的分析 3
1.2.1 文件加密使用RSA的可行性 3
1.2.2 文件加密使用RSA的意义 4
第2章 RSA文件加密软件的设计与实现 6
2.1 需求分析与总体设计 6
2.1.1 功能分析 6
2.1.2 工程方案选择 7
2.2 各部分的设计与开发 8
2.2.1 实现RSA加密算法的C++核心类库 8
2.2.2 封装C++核心类库的DLL组件 18
2.2.3 引用DLL的.Net类与实现文件操作功能的窗体应用程序 19
第3章 软件整体测试与分析改进 20
3.1 编写测试各项性能需要的精确计时类 20
3.2 测试数据与分析改进 20
3.2.1 密钥生成测试 20
3.2.2 数据输入输出测试 23
3.2.3 加密解密测试 23
3.2.4 性能分析与改进优化 26
3.3 使用中国余数定理 27
第4章 可移植模块的简要说明与开发前景 29
结束语 30
谢 辞 31
参考文献 32
附 录 33

前 言
RSA公钥加密算法是第一个既能用于数据加密也能用于数字签名的算法。它易于理解和操作,也十分流行。算法的名字以发明者的姓氏首字母命名:Ron Rivest, Adi Shamir 和Leonard Adleman。虽然自1978年提出以来,RSA的安全性一直未能得到理论上的证明,但它经历了各种攻击,至今(2006年)未被完全攻破。随着越来越多的商业应用和标准化工作,RSA已经成为最具代表性的公钥加密技术。VISA、MasterCard、IBM、Microsoft等公司协力制定的安全电子交易标准(Secure Electronic Transactions,SET)就采用了标准RSA算法,这使得RSA在我们的生活中几乎无处不在。网上交易加密连接、网上银行身份验证、各种信用卡使用的数字证书、智能移动电话和存储卡的验证功能芯片等,大多数使用RSA技术。
当今公钥加密更广泛应用于互联网身份认证,本课题将公钥加密算法RSA应用于小型文件加密。将任意文件加密成文本的解决方案,使其使用更加灵活。整个工程的分层设计,给引用移植和后续开发带来便利。

第1章 RSA应用现状及应用于文件加密的分析
1.1 RSA算法介绍与应用现状
RSA算法可以简单叙述如下:
<密钥生成>
取素数p,q,令n=p×q.
取与(p-1)×(q-1)互素的整数e,
由方程d×e=1 (mod (p-1)×(q-1))解出d,
二元组(e,n)作为公开密钥,
二元组(d,n)作为私有密钥.
<加密解密>
b=ae mod n,c=bd mod n.
附录中给出了证明a=c (mod n).
(具体的RSA算法协议见http://www.di-mgt.com.au/rsa_alg.html ,提及的算法中的字母与协议文档中的一致,不再另做解释)
RSA公开密钥加密算法自20世纪70年代提出以来,已经得到了广泛认可和应用。发展至今,电子安全领域的各方面已经形成了较为完备的国际规范。RSA作为最重要的公开密钥算法,在各领域的应用数不胜数。RSA在硬件方面,以技术成熟的IC应用于各种消费类电子产品。
RSA在软件方面的应用,主要集中在Internet上。加密连接、数字签名和数字证书的核心算法广泛使用RSA。日常应用中,有比较著名的工具包Open SSL(SSL,Security Socket Layer,是一个安全传输协议,在Internet上进行数据保护和身份确认。Open SSL是一个开放源代码的实现了SSL及相关加密技术的软件包,由加拿大的Eric Yang等发起编写的。相关详细介绍见http://www.openssl.org/about/ )。Open SSL应用RSA实现签名和密钥交换,已经在各种操作系统得到非常广泛的应用。另外,家喻户晓的IE浏览器,自然也实现了SSL协议,集成了使用RSA技术的加密功能,结合MD5和SHA1,主要用于数字证书和数字签名,对于习惯于使用网上购物和网上银行的用户来说,几乎天天都在使用RSA技术。
RSA更出现在要求高度安全稳定的企业级商务应用中。在当今的企业级商务应用中,不得不提及使用最广泛的平台j2ee。事实上,在j2se的标准库中,就为安全和加密服务提供了两组API:JCA和JCE。 JCA (Java Cryptography Architecture)提供基本的加密框架,如证书、数字签名、报文摘要和密钥对产生器; JCA由几个实现了基本的加密技术功能的类和接口组成,其中最主要的是java.security包,此软件包包含的是一组核心的类和接口,Java中数字签名的方法就集中在此软件包中。JCE(Java Cryptography Extension) 在JCA的基础上作了扩展,JCE也是由几个软件包组成,其中最主要的是javax.crypto包,此软件包提供了JCE加密技术操作API。javax.crypto中的Cipher类用于具体的加密和解密。在上述软件包的实现中,集成了应用RSA算法的各种数据加密规范(RSA算法应用规范介绍参见: http://www.rsasecurity.com/rsalabs/node.asp?id=2146 ,这些API内部支持的算法不仅仅只有RSA,但是RSA是数字签名和证书中最常用的),用户程序可以直接使用java标准库中提供的API进行数字签名和证书的各种操作。
单机应用程序使用RSA加密尚比较少见,例如使用RSA加密任意一个文件。
1.2 RSA应用于文件加密的分析
1.2.1 文件加密使用RSA的可行性
通过1.1节的论述,不难看出RSA当今的应用多在于数字签名和证书等方面。之所以只应用于这些短小数据的加密解密,是因为RSA算法加密极慢,速度是DES对称密钥加密速度的千分之一左右。正是因为这样,把RSA应用于普通文件加密的想法一直被忽略。通常文件被想象成大数据块,但是实际上在日常应用中,有些极其重要的文本资料是并不太大的,比如因担心遗忘而用普通文本记录的银行帐号和密码、不应被陌生人知道的重要电话号码、几千字节大的重要小图片等。
虽然RSA加密运算的速度十分慢,但是在PC性能越来越好的今天,对于几千字节的数据进行一次几百位密钥的RSA加密,所消耗的时间应该是可以接受的。下面结合大数运算程序的调试,从理论上简单的分析消耗时间。在一台普通配置的PC机上对一个整数进行幂模运算,因为公开密钥的e通常取的较小,所以指数取一个小整数,比如C353,模一个70字节长的整数(140位十六进制,大数单元以线性组方式实现,对应到RSA算法中,这相当于约560bit的n),调试一个函数测试,按初等数论中的知识对程序进行算法优化,最终在一台配置为AMD Athron2800+,外频333MHZ,物理内存512MB的PC上测试需要约45毫秒时间。如果按这种速度,逐字节对1KB的数据进行同样的运算,所消耗的时间理论上为45毫秒的1024倍即约45秒。这个时间并不是非常长。
其实从一个简单的角度来说,既然RSA用于数字签名可行,那就完全可以用于同样大小的普通文件。对于较大的文件,如果分成与数字签名同样大小的段(这里假设数字签名较短,不分段一次计算加密完成),分开的各段逐一进行加密运算,那所需要的时间也只是按文件大小线性的增长。通常数字签名为几十字节,加密运算并不需要很长的等待,这就说明对于几百字节或一两K字节大小的文件来说,如果进行RSA加密,并不会是非常漫长的工作。当然,如果文件更大,加密就显得十分漫长了。比如按前面叙述的45毫秒大数运算程序推理,加密1M字节大小的文件需要约1天的时间。所以,要在普通PC用几百位以上的长密钥RSA加密文件,文件不能过大,一般可以接受的上限是几KB。如果要在较短时间内加密大文件,需要缩短密钥长度以减小运算量,这将带来安全性隐患。
本文的第3章将根据实际调试好的软件,测试给出具体的时间消耗数据。例如,在一台配置为AMD Athron2800+,外频333MHZ,物理内存512MB的PC上测试实现的软件,以560bit的n逐字节加密一个1KB大小的文件需要55秒。通常记录如银行帐号密码等重要数据的文本文件大小不足百字节,加密只需要数秒钟。所以对于小型文件,进行较长密钥的RSA加密是完全可行的。
1.2.2 文件加密使用RSA的意义
如1.2.1节所述,小型文件加密可以使用RSA。比如,因担心遗忘而用普通文本记录的银行帐号和密码、不应被陌生人知道的重要电话号码、几千字节大的重要小图片等。可行的方法未必是必要的,本小节讨论何种文件适合用非对称密钥加密,即RSA加密文件的意义所在。
对于前面叙述的带有重要信息的小型文本和二进制数据的维护,①如果不加密,将无法放心的保存在计算机上,尤其是连网的或机房里的公共计算机。②如果借助功能强大的大型多用户数据保护程序维护几个小型文件,显得十分烦琐,好比杀鸡用牛刀。③如果采用对称密钥加密,即加密解密的密钥相同,只适合部分情况。在某些情况下,使用对称密钥加密文件,交流使用不够方便。比如,张三由于某种原因,需要将自己的某个文件在公共计算机上留给李四,而不希望别人看到内容。如果采用对称密钥加密,张三和李四提前约好一个密码就可以。但是如果张三想要在同一台公共计算机上再留一个秘密文件给王五,而不希望别人看到,就要和王五另外约定一个密码。如果需要在这台公共计算机上留十个文件给不同的人,自己就要记和十个人约定好的密码,这样以来交流起来不够方便,因为对于张三,要自己维护太多的密钥。非对称密钥(公开密钥方式)恰好解决这样的问题。只要大家都在这台计算机或这台计算机可以访问到的地方,留下自己的公开密钥,一切就变的容易解决了。张三要留给李四的文件,就用李四的公开密钥加密,要留给王五的文件,就用王五的公开密钥加密。李四和王五只要把留给自己的文件用自己的私有密钥解密,就可以得到留给自己的文件了。显然,非对称密钥体制更适合多用户交流,而将这种加密方式直接应用于文件加密,使我们在公开场合的交流更加灵活方便。
一种更实际的情况是,我们想通过Internet上的公众论坛或邮件发送重要保密信息给某人。例如发送一个银行帐号和密码给某人。这种情况要保证安全,在当今互联网络上是比较棘手的。①如果用公众论坛直接留言给指定用户,论坛管理员和服务器管理员通常有方法看到数据。②如果发送邮件,虽然传送过程是加密的,但是密码毕竟是由邮件服务器维护,所以系统管理员通常也有办法看到内容。问题的关键在于我们所有的数据包括密钥保存在服务器之上。在这种情况下,我们需要使用公开密钥方式,并自己维护私有密钥。RSA文件加密可以灵活的解决这些问题。例如,我们可以将任意一个文件用某人的公开密钥加密变换成一段可以复制粘贴的文本,然后粘贴在公众互联网上,对方只需把需要解密的文本复制保存成一个文本文件,在本地机用自己的私有密钥解密即可。我们可以将自己的私有密钥通过DES加密后保存在自己的移动磁盘上,使用的时候只要将其解密读取即可,用完后立即从当前操作环境清除。这样,我们自己维护自己的私有密钥,利用简单并且公开的方式,可以安全传送任意小型数据,包括一切二进制文件。
所以,对于使用小型文件进行数据交换的情况,更好的方案是通过一个小型应用程序对这些文件进行非对称密钥加密。为了适合前面叙述的在公共BBS与特定的某人交流重要保密信息的情况,加密生成的数据应该是文本,这样可以方便复制粘贴。
综上所述,使用前面叙述的方式加密文件有两点重要意义:①应用非对称密钥加密任意文件,使非对称密钥的应用不仅仅局限于互联网络。②非对称加密后的数据变换成文本,使得我们可以通过几乎任何方式安全传递任意文件,比如在只有http的环境使用xml方式。
第2章 RSA文件加密软件的设计与实现
2.1 需求分析与总体设计
2.1.1 功能分析
经过1.2.2节的论述,我们可以将对软件的要求总结如下:
① 可以按要求的位数生成非对称密钥。
② 可以保存密钥和装载密钥,密钥保存为纯文本。
③ 可以用指定密钥以RSA算法加密任意一个文件,加密生成的数据为纯文本。
④ 可以装载加密过的文件,并用指定的密钥解密还原出原文件。
⑤ 提示信息完整、操作舒适、图形界面雅观
按上述描述,给出Use Case和Statechart如图2-1。

图2-1 本项目的 Use Case和Statechart
根据以上分析,一般来说,需要进行编码的程序有
①RSA密钥生成 ②RSA加密解密 ③任意文件的读取和保存操作 ④各环节必要的数据编码转换 ⑤图形操作界面。
2.1.2 工程方案选择
结合现有的常见开发模式综合分析,有多种实现方案,下面陈述其中几种,并分析选择一种解决方案,并给出工程框架。

  1. 整个工程使用java平台实现
    RSA密钥生成、RSA加密解密的功能实现十分简单,因为标准库中集成几乎所有功能,不需要从RSA算法出发进行编码。在j2se标准库中,javax.crypto中的Cipher类用于具体的加密和解密,java.security包直接提供了数字签名的相关方法。因为有强大的标准库支持,文件的读取和保存操作、各环节必要的数据编码转换、图形操作界面的实现也很简单(使用java.io java.awt或javax.swing 等包),如果结合一种快速开发的IDE,比如JBuilder,整个软件可以在很短的时间内编码完成。如果不考虑非PC设备和机器效率等问题,java平台几乎是最佳解决方案。但是缺点也很明显,如果想把核心算法和功能应用到非PC设备(例如嵌入式手持设备),则要求设备上有支持前面提及的加密类库的CVM;对于在PC上运行,JVM的数据运算速度要远远落后于本地化代码在PC上的运算速度,本软件需要进行大量运算,这一点不适合由java完成。
  2. 整个工程使用.Net平台实现
    与使用java平台完全类似,加密等有.Net基础类库的支持,不需要大量编码实现,另外由于Visual Studio的强大便利,这种规模的工程可以十分迅速的完成。缺点是只能在有微软.Net Framework的环境运行,在Windows操作系统,.Net Framework的机器效率好于java平台,但是相比于本地化的代码,还是十分拖沓的。
  3. 整个工程使用Windows本地化程序实现
    在不应用Windows或第三方现成组件的情况下,需从RSA算法出发编码实现。其他各功能的设计开发,如文件操作、数据编码转换和图形界面等,可以使用ATL、MFC或Windows API实现。这种工程几乎是为Windows量身订做,执行效率最好。但是对于非PC设备,只能方便的移植到运行Windows嵌入式操作系统的设备,向其他操作系统移植困难,需要重新编写大量代码。通常解决本地化代码的移植问题,都是使用C++标准库,即功能尽量多的由C++标准库完成,这样在移植的时候,只需要重新编写操作系统相关的代码即可。这种开发方式比起前两种,缺点就是设计开发模式陈旧,代码烦琐,不方便维护;流行的.Net上的语言引用各种功能比较麻烦。
  4. 考虑可能的复用,针对具体情况分层开发实现
    综合考虑复用性、可维护性和执行效率,较妥当的方法是分层设计。核心的RSA算法由C++类库实现,针对用户所在的操作系统封装成本地化组件。其他各功能如文件操作、数据编码转换和图形界面等,由托管代码借助虚拟机平台标准库的功能快速开发实现(本文针对选用.Net上的C#论述,选用java由JNI或其他方式调用本地组件,设计模式上是完全类似的)。这种开发方式,核心功能集中在最底层,在不断的封装中针对具体环境对组件功能不断扩充,任意一个层面的封装都可以被直接应用到其他项目,比如在Web使用以前为某窗体程序写的组件、给嵌入式设备交叉编译算法库等。但是每一层都需要依赖底层的所有组件。图2-2形象的说明了分层设计给复用带来的好处。

图2-2 综合考虑复用性、可维护性和执行效率的分层设计
选用第四种设计方案,上层使用C#,底层算法使用C++,可以由一个Visual Studio解决方案管理,给调试带来极大的方便。整个工程分四层,实现RSA加密算法的C++核心类库、封装C++核心类库的DLL组件、引用DLL的.Net类、实现文件操作功能的.Net窗体应用程序。2.2节详细介绍各部分的设计与开发。
考虑到工作量,本软件加解密数据没有严格遵从RSA标准PKCS #1,而是在满足设计要求的前提下,以一种尽可能简单的方式实现加密和解密。
2.2 各部分的设计与开发
2.2.1 实现RSA加密算法的C++核心类库

  1. 大数存储和四则运算
    根据RSA算法的要求,为了实现大数的各种复杂运算,需要首先实现大数存储和基本四则运算的功能。当今开源的大数运算C++类有很多,多用于数学分析、天文计算等,本文选用了一个流行的大数类型,并针对RSA算法和本项目的具体需要对其进行了扩充和改进。下面简单介绍大数存储和四则运算的实现原理。
    最先完成的功能是大数的存储,存储功能由flex_unit类提供。和普通的类型一样,每一个大数对应一个flex_unit的实例。类flex_unit中,用一个无符号整数指针unsigned * a指向一块内存空间的首地址,这块内存空间用来存储一个大数,所以可以说,大数是被存储在一个以unsigned为单元的线性组中。在方法void reserve( unsigned x )中通过C++的new来给a开辟空间,当flex_unit的实例中被存入比当前存储的数更大的数时,就会调用reserve来增加存储空间,但是当flex_unit的实例中被存入比当前存储的数更小的数时,存储空间并不会自动紧缩,这是为了在运算的时候提高执行效率。结合指针a,有两个重要的无符号整数来控制存储,unsigned z和unsigned n,z是被分配空间的单元数,随数字变大不断增大,不会自己紧缩,而n是当前存储的大数所占的单元数,组成一个大数的各unsigned单元的存入和读出由set、get方法完成,变量n是只读的。类型unsigned在32位机是32位的,所以对于flex_unit这个大数类来说,每个大数最大可以达到 个字节长,这已经超过了32位机通常的最大内存容量,所以是足够进行RSA所需要的各种运算的。图2-3形象的说明了大数存储类flex_unit对大数的管理。

图2-3 flex_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,可采用如图2-4的算法流程计算 。

图2-4 幂模运算分解为乘模运算的一种流程
按照上述流程,列举两个简单的幂模运算实例来形象的说明这种方法。
① 求的值
开始 D = 1 P = 2 mod 17 = 2 E = 15
E奇数 D = DP mod n = 2 P = PP mod n = 4 E = (E-1)/2 =7
E奇数 D = DP mod n = 8 P = PP mod n = 16 E = (E-1)/2 =3
E奇数 D = DP mod n = 9 P = PP mod n = 1 E = (E-1)/2 =1
E奇数 D = DP mod n = 9 P = PP mod n = 1 E = (E-1)/2 =0
最终D = 9 即为所求。
② 求的值
开始 D = 1 P = 2 mod 17 = 2 E = 8
E偶数 D = 1 P = PP mod n = 4 E = E/2 =4
E偶数 D = 1 P = PP mod n = 3 E = E/2 =2
E偶数 D = 1 P = PP mod n = 9 E = E/2 =1
E奇数 D = DP mod n = 9 P = 不需要计算 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=C mod n;
for i=0 to u do
{
if(Ei=1)D=DP(mod n);
   P=P
P(mod n);
   }
   return D;
}
有些文献将上述算法称为平方乘积二进制快速算法,例如参考文献中的《基于RSA算法的一种新的加密核设计》,其实这种算法本质上和图2-4的流程完全一致,只是把根据指数奇偶分开的减一和除以二合并成对指数二进制各位的判断而已。在本软件的代码中采用直接扫描vlong二进制各位的办法。
剩下的问题就是乘模运算了。提高乘模运算的速度是提高模幂运算速度的关键。一般情况下,n是数百位乃至千位以上的二进制整数,用普通的除法求模而进行乘模运算是不能满足速度的要求的。为此,Montgomery在1983年提出了一种模加右移的乘模算法(主要著作发表于1985年),从而避免了通常求模算法中费时的除法步骤。本软件仅仅是应用Montgomery(蒙哥马利)算法,算法的具体推导证明需要颇多数论知识,不在本文的讨论范围内,如需了解可参见蒙哥马利的相关著作。下面简单描述RSA中常用的Montgomery(蒙哥马利)算法供参考理解源程序。
选择与模数n互素的基数R=2k,n满足2k-1≤n<2k, n应为奇数。并且选择R-1及n’,满足0< R-1<n, 0< n’<n,使得 RR-1-nn’=1。对于0≤m<Rn的任意整数,Montgomery给出求模乘法mR-1 mod n 的快速算法M(m):
M(m)
{

if (t≥n) return (t-n);
else return t;
}
因为,故t为整数;同时,得。由于,M(m) 中t结果范围是0≤t<2n,返回时如果t不小于n,应返回t-n。
本软件程序中,RSA核心运算使用的乘模算法就是 M(AB)。虽然M(AB)并不是乘模所需要的真正结果,但只要在幂模算法中进行相应的修改,就可以调用这个乘模算法进行计算了。本软件起初未使用Montgomery 乘模算法时,加密速度比使用Montgomery乘模算法慢,但速度相差不到一个数量级。
将上述乘模算法结合前面叙述的幂模算法,构成标准Montgomery幂模算法,即本软件所使用的流程,叙述如下。
M(m)
{
k = ( m * n’ ) mod R;
x = (m + kn ) / R;
if (x>=n) x -= n;
return x;
}
exp(C,E,n)
{
       D=R-n;
       P=C
R mod n;
i=0;
       while(true)
          {
              if(E的当前二进制位Ei1)D=M(D*P); //从低位到高位检测二进制位
i+=1;
if(i
E的二进制位数)break;
             P=M(PP);
        }
   return D
R-1 (mod n);
}
在具体的实现中,对应monty类的mul和exp方法。全局函数modexp初始化monty对象并调用其exp方法,使用的时候直接调用modexp即可。
3. 寻找素数•Eratosthenes筛选与Fermat素数测试
首先要说明的是,事实上,当今的计算机还不足以聪明到立刻计算生成一个很大的随机素数。一般来说,要得到100%准确的大素数,都是通过查已经计算好的素数表的方式。但是素数表的方式给RSA的安全性带来隐患,因为攻击者如果得到了密钥生成时所使用的素数表,攻破RSA加密的难度将会大大降低。本程序起初使用素数表的方式,后来考虑到安全性问题,生成密钥的方式改为随机计算生成。这样,短时间内如果要得到一个100%准确的大素数是很困难的,只能以尽可能高的概率得到一个大素数。
经过2.2.1.1和2.2.1.2小节,所有的大数运算功能都准备完毕,在此基础上,本工程将寻找素数的功能置于类Prime_factory_san之中。外部只要调用本类实例的成员vlong find_prime( vlong & start )就可以以大数start为起点,得到一个数,这个数是素数的概率很大。下面介绍寻找素数的原理。
首先在需要寻找素数的整数范围内对整数进行筛选,把所有确知为合数的整数排除出去。程序中构造了一个数组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<np;i++)
{
unsigned p = pl[i];
unsigned r = start % vlong§;
if ® r = p - r;
while ( r < SS )
{
b[r] = 0;
r += p;
}
}
这里利用start对各小素数因子p求模的办法,得到当前p在素数搜索范围内的最小倍数在b[]中的对应位置,将其剔除后,不断后移p个位置,将这个小素数因子p在搜索范围内的所有倍数全部剔除,如图2-5所示。在完成对所有小素数因子的类似操作后,他们的倍数在搜索范围内的位置标记b[r]被全部标记为0。实际上这就是Eratosthenes筛选法。

图2-5 在素数搜索范围内剔除小素数因子p的倍数
接下来,对可能为素数的数(即标记数组b[]中值为1的元素对应的数)进行素数测试。数论学家利用费马小定理研究出了多种素数测试方法,本程序使用一种最简单的方式,直接应用费马小定理。取一个与p互素的整数A,对于大素数p来说应该满足Ap-1mod p=1,但是我们把p代入一个大整数,满足这个关系的数不一定是素数。这时我们改变A,进行多次测试,如果多次测试都通过,这个数是素数的概率就比较大。按这种原理,我们编写素数测试函数如下。
int is_probable_prime_san( const vlong &p )
{
const rep = 4; //测试次数
const unsigned any[rep] = { 2,3,5,7 }; //测试用的底数
for ( unsigned i=0; i<rep; i+=1 )
if ( modexp( any[i], p-vlong(1), p ) != vlong(1) ) return 0;
//modexp是幂模函数,按上一小节叙述的算法编码。
//这里modexp计算any[i]p-1mod p。
return 1;
}
测试通过,程序就判定这个数为找到的素数,将找到的素数返回给上层程序使用。在这里其实有一个不可忽视的问题,就是得到一个测试通过的合数。对于这种情况,RSA算法加密解密是否还可以实现,是一个需要从数学角度论证的问题。因为得到素数的概率很高,经过一整天的生成密钥和加密操作,没有发现失败的密钥, 所以本文暂没有对这个问题进行讨论。
综上所述,总结素数寻找的流程,如图2-6所示。

图2-6 函数find_prime寻找素数的流程框图
得到了大素数,即RSA算法中的p、q,我们就可以计算出密钥,进行加密等操作了。
4. 二元一次不定方程
在RSA 算法中,往往要在已知A、M的情况下,求B的最小值,使得 (AB) mod M = 1。即相当于求解B、N都是未知数的二元一次不定方程 AB-MN=1的最小整数解。
而针对不定方程ax-by=1 的最小整数解,古今中外都进行过详尽的研究,西方有著名的欧几里德算法,即一种辗转相除法,中国有秦九韶的“大衍求一术”。欧几里德算法是一种递归算法,较容易理解。下面举例说明用欧几里德算法求解二元一次不定方程的最小整数解。
给定不定方程11x-49y=1,求最小的x
(1) 11 x - 49 y = 1 49 mod 11 = 5
(2) 11 x - 5 y = 1 11 mod 5 = 1
(3) x - 5 y = 1 5 mod 1 = 0
逆向代入:
令y=0 代入(3)得x=1
令x=1 代入(2)得y=2
令y=2 代入(1)得x=9
x=9;y=2即为所求。
程序中,全局函数vlong modinv( const vlong &a, const vlong &m )用来完成这种算法。对应前面的叙述,参数a对应A,参数m对应M,函数返回值即为B的最小值。
5. 按常规RSA算法实现加密与解密
最后,类RSA_san基于前面的准备工作,实现RSA密钥生成和加解密的功能(算法在此不再赘述,RSA算法协议见http://www.di-mgt.com.au/rsa_alg.html)。为了方便阅读,整个类的源程序中,所使用的变量字母均和RSA算法协议中一致。在类RSA_san的构造函数里,执行准备一对随机密钥的操作。之后可以直接使用类的其他成员进行RSA加解密操作,也可以载入以前保存的密钥或再次随机生成密钥。类中各成员频繁的用到字符串和vlong类型的转换,因为大数是用字符串置入的,而把大数读出,也是保存在字符指针指向的一段内存空间里,所以也是字符串。所以,需要实现一系列的编码转换函数,比如将unsigned指针指向的一段空间里保存的一个大数,表示成十六进制形式的字符串文本。编码转换通常是用C风格的指针操作和sprintf函数来完成。
需要加密和解密的数据也是通过字符串参数置入的。由于字符串的结尾字符“\0”实际上也可能是需要加密的数据,所以置入的串长度并不能以“\0”来决定,程序里引入一个unsigned类型的参数来决定置入的串长度,这样就解决了加密连0数据时候被截断的问题。
因为是对文件加密的软件,需要加密的数据通常并不止几字节,这时由上层程序将数据按用户的设置分块,分别加密或解密。本软件默认的分块大小是1字节,即逐个字节作为参数,调用C++核心模块中的方法。
加密解密流程均为标准RSA算法,具体过程和使用方法参见源程序和接口文档。
6. 核心类库综述
综上几小节所述,实现RSA加密算法的C++核心类库由六个类组成,类名和对应的功能描述总结如表2-1所示。各个类之间的关系如图2-7所示。

表2-1 RSA加密算法的C++类库中的类
class flex_unit 大数运算和存储最基本的类,主要实现超大整数的存储和索引管理。
class vlong_value 是flex_unit的派生类,在灵活大数存储的基础上实现四则运算函数。
class vlong 以vlong_value为基础(将一个vlong_value指针作成员),重载运算符。
class monty 为RSA准备大数求幂模运算的函数,vlong的友元。
class Prime_factory_san 素数工厂,寻找大素数的类。
class RSA_san 在前5个类的基础上,实现RSA核心算法的类。

图2-7 C++核心功能类图
另外需要说明的是,程序中有几个不属于任何类的全局函数,比如应用辗转相除法求最大公约数的函数gcd、解同余方程的函数modinv等。按常规设计模式来说,不应当出现类之外的函数,但是因为这些函数使用频繁,考虑到机器效率,直接置于全局,不再另行包装。
2.2.2 封装C++核心类库的DLL组件
在Visual Studio当前的解决方案中以VC++创建一个win32dll工程,将测试好的实现RSA加密算法的C++核心类库中的所有文件加入到此工程下,新建一对cpp和h文件,把可能用到的功能全部规划为新文件中的全局函数,并以C接口导出,即__declspec(dllexport)。由于核心类库的对外功能都使由RSA_san类提供的,所以在新cpp文件中全局的声明一个RSA_san类的对象指针(RSA_san WRSA),全局函数int start_RSA_san()初始化WRSA对象,在初始化成功后,其他全局函数通过调用*WRSA对象的公开方法实现各种功能,如加密、读取密钥等。在关闭上层引用程序以前,应执行int finish_RSA_san()来释放WRSA,该函数执行delete WRSA的操作。其他接口函数的使用见DLL接口文档。
另外,DLL组件可以自己在全局函数中实现一些其他功能,作为对核心类库功能的补充。C接口的DLL组件可以被诸如VB、Delphi等开发环境方便的引用。
2.2.3 引用DLL的.Net类与实现文件操作功能的窗体应用程序
在C#编写的.Net类里,使用特性[DllImport(“sanpack_rsa.dll”)]引用C接口的DLL组件。类中接口DLL的函数都以静态成员的方式对外公开,其他.Net程序可以直接使用。在类库中还提供了任意长度随机串的生成函数,此函数用于生成寻找素数的大数起点。
文件操作使用.Net基础类库中的System.IO中的类实现。一般因为文件操作十分简单,用流输入输出的方式包装完成,程序中将文件操作直接放在菜单项关联的事件处理函数中。
窗体等图形操作界面直接由Visual Studio的所见即所得的方式完成,不需要编码实现。
最终实现的应用程序,结构如图2-8所示。

图2-8 本软件的Visual Studio解决方案

第3章 软件整体测试与分析改进
3.1 编写测试各项性能需要的精确计时类
由于.Net基础类库提供的计时功能十分不精确,无法胜任软件性能测试的工作,这里使用Windows API 函数QueryPerformanceCounter和QueryPerformanceFrequency进行精确计时。功能被封装在C#类HighResolutionTimer中,使用时只需构造一个此类的对象,在计时开始的时候调用其Start方法,计时结束时调用其Stop方法,然后访问其ElapsedTime属性,就可以得到一个以秒为单位的float型精确的计时值了。API 函数QueryPerformanceCounter和QueryPerformanceFrequency是靠查询CPU的高精度计时器来计时的,所以可以轻松的精确到毫秒级计时。
附录中给出了这个类的源代码。
3.2 测试数据与分析改进
3.2.1 密钥生成测试
生成密钥运算最费时的工作是寻找素数。如2.2.1.3小节所叙述,寻找素数是一项颇为复杂的工作,其速度可能受以下变量的影响:RSA加密需要的n的位数(寻找素数的整数起点大小start)、大素数测试时底数A的个数(针对一个整数的素数测试次数)、小素数因子p的个数NP、一轮寻找遍历的整数个数SS等。其中最具影响力的因素显然是RSA加密需要的n的位数。以下对各变量分别进行测试,暂且忽略操作系统调度对测试的影响。

  1. 测试加密使用的n的位数对耗时的影响
    即 在固定A、NP、SS等变量的情况下,改变加密位数n,测试密钥生成的时间消耗情况。测试时,A取4个值,分别为2、3、5、7,NP取200,SS取1000。测试PC配置为CPU CR1.7GHZ/外频100MHZ/物理内存512MDDR/MSI6398主板845 Ultra-AD芯片组,下文测试中,未说明PC配置的也都在同一PC完成,不再重复。统计数据如表3-1所示。表中各项对应的全部测试数据见http://3mn.net/www/download/RSAkeygen_n-Ttest.txt ,包括两个作为素数搜索起点的随机数和生成的素数p、q以及e、d、n。
    表3-1 RSA加密模数n与密钥生成耗时的关系
    加密位数n (bit) 对应的搜索起点字节数 测试5次获得随机密钥消耗的时间(秒) 平均时间消耗(秒)
    第一次 第二次 第三次 第四次 第五次
    256 16 0.7968 0.5448 0.6000 0.6024 0.7899 0.6668
    384 24 1.3858 1.8130 1.4065 2.2514 1.4137 1.6541
    512 32 2.5346 2.4067 2.6756 4.5683 1.9542 2.8279
    640 40 4.3264 3.5319 7.3716 5.8340 2.8486 4.7825
    768 48 10.1956 6.9521 5.3732 6.6958 4.1091 6.6652
    896 56 6.6307 5.1443 14.0495 14.7396 11.3944 10.3863
    1024 64 6.2941 17.1003 12.3989 11.5311 11.0922 11.6833
    1152 72 20.3097 15.8271 23.7094 16.2601 13.1304 17.8473
    1280 80 26.6514 11.5004 54.1937 13.3371 18.3094 24.7973
    不同颜色的单元格表示每行中的最大值和最小值 这种颜色代表行中最大值 这种颜色代表行中最小值

观察表3-1上的统计数据,很容易发现随着加密位数的增加,密钥生成需要的时间显著增加。在测试范围内,随着加密位数增大,每一行中的最大最小值差距也呈粗略的增大趋势。也就是说对于长密钥来说,RSA随机生成密钥消耗时间的可能范围较大。这是因为对于大整数来说,可能出现在较长一段区间中没有素数的情况。
在较常用的1024位RSA加密时,用本软件的算法,测试时最长出现了17秒多的计算,虽然这对于用户来说时漫长的等待,但是考虑到安全性,还是舍弃了素数表和密钥库的方案,而使用大素数随机生成,用户可以把生成的私钥单独加密保存在可靠的存储空间内,以获得更高的安全性。
表3-1仅能从实验的角度直观理解,具体到一次密钥生成的运算,所需要的时间是很不确定的,比如,一次1280位的密钥生成,需要的时间完全可能比一次896位的密钥生成时间短,由于素数分布规律非常奥妙,加上测试运算需要的时间颇长,这里很难给出对于一个具体位数的密钥生成所需时间的统计模型。
另外需要说明的是,表3-1的加密位数在实际软件设置时并不严格。这是因为,实际作为参数设置的是两个大素数的搜索起点。如果随机生成的起点整数大小比较接近更长一位的整数的话(例如FFFF很接近10000),向后寻找所得到的素数很可能长出一位。而且,两个k位长的整数相乘的结果也未必是2k位,比如100*100=10000,相乘结果是2k-1位。所以,在表3-1实际测试填写时,加密位数可能会有几位的差距,但是这不碍大局。
2. 测试底数A对耗时的影响
为了保证生成素数的成功率,A至少要有4个。如果少于4个,则素数测试失败的可能性比较大,经过测试发现不可以忽略。2.2.1.3小节曾经提到,如果素数测试通过了合数,就可能产生错误的密钥,使加密解密操作失败。所以测试A的时候,最少有让其取4个值。而取6个值以上,测试算法失败的概率已经非常小,没有什么实用意义,所以这里测试A从4个到6个的情况。固定其他变量:n取512位和1024位(即素数搜索起点位数设置为32和64),NP取200,SS取1000。从理论上说,对于同样的起点,素数测试次数越多,需要的时间就越长。实际测试结果如表3-2所示(其中A取4个的情况直接从表3-1复制数据,不再另做测试),表中各项对应的全部测试数据见http://3mn.net/www/download/RSAkeygen_A-Ttest.txt ,包括两个作为素数搜索起点的随机数和生成的素数p、q以及e、d、n。
表3-2 素数测试底数A对密钥生成时间的影响
A的设置 测试5次 n为512bit时密钥生成需要的时间(秒) 平均耗间(秒)
2,3,5,7 2.5346 2.4067 2.6756 4.5683 1.9542 2.8279
2,3,5,7,11 3.3030 2.5838 3.7744 2.4474 2.3716 2.8960
2,3,5,7,11,13 2.3906 3.8213 2.2279 1.9119 2.4995 2.5702
A的设置 测试5次 n为1024bit时密钥生成需要的时间(秒) 平均耗间(秒)
2,3,5,7 6.2941 17.1003 12.3989 11.5311 11.0922 11.6833
2,3,5,7,11 24.0268 27.0971 10.0254 20.1331 7.8633 17.8291
2,3,5,7,11,13 7.1299 17.7117 28.4306 27.8631 8.7279 17.9726
由表3-2可以看出,对于512bit密钥,A取从4个到6个,对随机密钥的产生时间影响不大。但是对于较长的1024bit密钥,A取4个和A取6个值,密钥生成时间产生明显差距,A取6个值时生成随机密钥需要的平均时间比A取4个值时长数秒之多。为了同时保证密钥生成速度和素数的准确程度,我们在实际使用时取A为5个值,即2、3、5、7、11。
3. 测试小素数因子个数NP对耗时的影响
固定其他变量:A取5个分别为2、3、5、7、11,n取512位和1024位(即素数搜索起点位数设置为32和64),SS取1000。测试结果如表3-3所示。表中各项对应的全部测试数据见http://3mn.net/www/download/RSAkeygen_P-Ttest.txt ,包括两个作为素数搜索起点的随机数和生成的素数p、q以及e、d、n。
表3-3 小素数因子个数NP对密钥生成时间的影响
NP 测试5次 n为512bit时密钥生成需要的时间(秒) 平均耗间(秒)
100 2.7550 1.7077 3.1189 3.0514 2.7971 2.6860
200 3.3030 2.8311 4.2693 2.8760 2.8581 3.2275
300 3.0029 2.1159 3.4268 2.2918 2.7531 2.7181
400 2.6944 5.6355 4.1228 3.6816 2.5232 3.7315
500 3.3725 4.3178 4.3426 3.7141 2.9172 2.8643
测试5次 n为1024bit时密钥生成需要的时间(秒) 平均耗间(秒)
100 5.7052 13.5294 9.9006 20.3994 23.3666 14.4542
200 13.7745 9.3998 11.3043 24.1689 7.7326 13.2760
300 7.4426 8.2489 29.1550 15.2442 27.4376 17.5056
400 17.8602 23.3219 17.7196 18.8298 11.5861 17.8635
500 13.9936 22.6667 14.7468 14.2334 12.5631 15.6407
不同颜色的单元格表示每行中的最大值和最小值 这种颜色代表行中最大值 这种颜色代表行中最小值
由于测试时间漫长,测试的数据量比较有限。这里并没有看出什么明显的规律。
而且通过本次测试还可以发现,表3-3中NP为200,n为1024bit测试的一行,变量设置和表3-2中A设置为2、3、5、7、11,n为1024bit的一行完全一致(对应还有一行n为512bit的数据变量设置一致),但是耗时平均差距相差4秒之多(512bit的一行差距不到1秒)。可见对于长密钥,同一种情况测试5个数据取均值并不能精确的说明问题,除非测试得到的数据有很明显的大幅差距,例如前面两段测试n的位数和A的个数的耗时影响情况。这里也正是因为前面提到的,对于大整数来说,可能出现在较长一段区间中没有素数的情况,使得同样设置的各次密钥生成耗时的可能范围很大,再加上大素数分布规律奥妙,观察5次测试结果的均值对于不很明显的规律显得意义不大。
实际使用中,设置NP值为200。
4. 测试SS对耗时的影响
同样未发现明显规律,在使用中设置SS为1000。
3.2.2 数据输入输出测试
主要测试文件的输入输出性能。实际上就是测试.Net基础类库中实现文件操作的System.IO中的StreamReader、StreamWriter等类的读写性能。直接在Visual Studio调试一个简单的C#文件读写程序,得到本软件中使用的文件操作方法的执行性能。在配置为CPU CR1.7GHZ/外频100MHZ/物理内存512M DDR/MSI6398主板845 Ultra-AD芯片组/UTA133 2M缓存硬盘的PC上,读入一个100KB的文件仅需要35毫秒,写出一个100KB的文件需要29毫秒。这样的时间消耗,相对于繁复的RSA计算所消耗的时间来说,是完全可以忽略不计的。
3.2.3 加密解密测试
进行对任意文件加密与解密的测试,这里给出几组从不同角度进行测试的数据。下面除了第3组测试,其他都是把文件逐字节进行RSA运算,逐字节加密是本软件的默认设置。加密时使用的测试文件、各密钥文件以及加解密后生成的文件可以从以下地址下载:http://3mn.net/www/download/RSAtestfiles.rar 内附说明。

  1. 用同样的密钥对不同大小的文件公钥加密、私钥解密,各自消耗的时间与待加密文件大小的关系
    随机生成两组密钥,一组n长512bit,一组n长1024bit。密钥具体数据见附录(n的实际位数有微小差距)。
    分别对一组不同大小的文件进行公钥加密。统计消耗时间情况如表3-4所示,统计数据以曲线表示如图3-1。
    表3-4 待加密文件大小与加密时间的关系(时间单位:秒)
    n位数 文件大小 50Byte 100Byte 150Byte 200Byte 250Byte
    512bit公钥加密 4.8537 9.7636 14.3205 18.9084 23.5322
    512bit私钥解密 9.5452 18.9207 28.1287 37.9556 46.5794
    1024bit公钥加密 12.6111 24.5664 36.5895 48.6288 60.6503
    1024bit私钥解密 40.0084 79.2507 120.4443 158.0028 198.365

图3-1 文件大小–加密时间曲线(逐字节)
从表3-4以及图3-1可以看出,使用同一公开密钥加密不同大小的文件,消耗时间随着文件大小的增加线性的增加,和1.2.1小节分析的完全一致。对于较大的文件,加密位数对时间的影响十分明显。对于250字节的文件来说,1024bit的公钥加密比512bit的耗时多1.5倍左右;1024bit的私钥解密比512bit的耗时多3倍以上。对于一定的加密位数来说,私钥解密所需要的时间比公钥加密需要的时间长。对于一定大小的文件,使用512bit的密钥,私有密钥解密需要的时间是公开密钥加密需要时间的2倍左右;而如果使用1024bit的密钥,私有密钥解密需要的时间是公开密钥加密需要时间的3倍以上。再测试几个1280bit的密钥加解密,发现私有密钥解密所需要的时间相对于公钥加密时间更长。可见,本软件密钥长度越长,私有密钥解密与公开密钥加密的耗时比越大,这和其他软件是一致的。因为根据PCKS #1的RSA的应用建议,e是比较短的,而d和n的长度差不多,这就使得求与d、n有关的幂模运算量比与e、n有关的幂模运算量大很多,而且随着n的增加,两组幂模运算的运算量差距也迅速加大。为了减少d、n幂模运算的时间消耗,考虑到使用中国余数定理分解简化运算,具体做法见3.3节。
2. 用同样的密钥对不同大小的文件公钥加密,加密后生成的文件大小与待加密文件大小的关系
在上一组测试进行完之后,我们得到了一些加密文件,下面详细分析这些加密后生成的文本文件大小。表3-5给出了加密后的各文件大小。
表3-5 本软件生成的加密文件大小测试(文件大小单位:Byte)
n位数 文件大小 50Byte 100Byte 150Byte 200Byte 250Byte
512bit公钥加密 6,557 12,977 19,397 25,817 32,237
1024bit公钥加密 12,986 25,835 38,684 51,533 64,382
从表3-5的数据可以发现,对于同样的文件进行加密,使用1024bit密钥加密所得到的文件大小是使用512bit加密的2倍左右。而且不论用哪组密钥进行加密,加密所生成的文件大小都比原来未加密的文件大。可以从两个角度来理解文件在加密后增大:(1) 因为模数n通常较大,所以加密幂模运算的结果通常很大,这使得逐字节加密时,密文比明文长很多。(2) 因为加密完以后保存成十六进制文本,对于十六进制文本来说,每字节可能出现的字符只有16个,所以字节熵是4bit;而对于任意数据来说,每字节的熵是8bit。所以同样的信息量,采用十六进制文本要比原始数据大一倍。从更简单的角度理解,每字节表示为两位十六进制,每位十六进制在文本中是一个符号,占用1字节,所以长度大了一倍。
从表3-5的数据还可以看出,对于同样的密钥,随着原始文件大小的线性增长,加密后的文件大小也基本呈线性增长。在使用512bit加密时,加密后的文件大小是加密前数据大小的130倍左右;在使用1024bit加密时,加密后文件大小是加密前数据大小的260倍左右。根据这些数据,可以总结出本软件使用的加密方式,加密前后文件大小的近似换算公式如下:

其中A是待加密文件数据长度,B是加密后文件长度,N是RSA加密位数,N>8。
以上公式其实也可以从理论上得到,因为模数n取8位时,幂模运算的结果仍然近似为8位,这时一个字节的数据经过加密,得到的数据大小近似不变,转换成十六进制文本,大小就增加了1倍。按此原理,幂模运算结果长度近似为加密模数n的长度,加密后数据长度是加密前的N/8倍(N是加密位数),而转换为十六进制文本后长度又增大1倍,加密后得到的文本长度就是加密前原始数据大小的N/4倍,所以B=A×N/4,这与前面从实验结果总结得到的公式基本一致,可以把公式中的260修正为更精确一些的256。
3. 以多字节为步长,对文件进行加密
默认的设置是加密时逐个字节进行RSA运算,可以通过设置窗体把分块的大小更改为其他长度,比如2字节一组、4字节一组,进行RSA运算。下面测试多字节为步长的加密执行效率。取一个480字节长的文件作为加密对象,对其进行512bit RSA公钥加密、私钥解密还原,记录所消耗的时间。统计数据如表3-6所示。
表3-6 加密分段大小改变对效率的影响测试(消耗时间单位:秒)
加密方式 步长 2字节 4字节 6字节 8字节 10字节
512bit公钥加密 23.5551 12.8205 7.8117 5.9185 5.1848
512bit私钥解密 46.3083 23.6089 16.1083 11.4820 9.2541
可见,增大加密步长使加密解密速度大幅增加,而且大步长的加密生成的文本文件体积也比小步长的小。这都是因为增大了步长后,文件被分成的块数少了,幂模运算次数下降。所以在使用RSA加密时,设置使用合适的数据分块也是提高加密速度的关键。
4. 在更快的PC,对进行文件加密测试
在一些性能更好的PC上,本软件可以获得更好的性能,测试数据同样可以分析得到以上段落叙述的结论。下面对照表3-4,给出一组其他PC上同样的测试得到的数据,测试PC配置为CPU AMD Athron2800+,外频333MHZ,物理内存512MB。数据见表3-7。
表3-7 待加密文件大小与加密时间的关系再次测试(时间单位:秒)
n位数 文件大小 50Byte 100Byte 150Byte 200Byte 250Byte
512bit公钥加密 2.4347 4.8975 7.1728 9.4508 11.9837
512bit私钥解密 4.7725 9.4427 14.002 19.0125 23.6544
1024bit公钥加密 6.3501 12.2364 18.2459 24.3001 30.1284
1024bit私钥解密 20.0101 39.8754 60.2126 79.3351 99.5482
对于这组数据,表3-4后的推理分析仍都成立。在此PC测试填写表3-6,同样得到了类似规律的数据,在此不再罗列。经过一系列各种机型、各种Windows操作系统(包括Windows XP/2000SP4/ME/98,均需.Net框架)上的测试,本软件均能正常运行。在2006年初主流配置的PC上运行此软件,逐字节加密1KB大小的文件,消耗时间均在1分钟以内。
3.2.4 性能分析与改进优化
经过一系列的RSA密钥生成、文件输入输出和加密解密测试,做简要的性能分析如下。
① 软件消耗时间的运算,大部分集中在C++核心类库,即RSA相关的各种运算。其中,幂模运算和寻找素数对时间的消耗最大,在核心优化时应优先考虑。
② 文件输入输出消耗时间其次,因为磁盘读写速度要远远低于内存读写速度。所以,应该将频繁的读写操作尽量集中到内存,然后一次性写入磁盘。

针对以上两点,软件应进行一系列改进和优化。主要有以下几方面。
① 在要对文件进行加密解密的时候,先将文件按一定的数据结构读入内存,然后进行加密或解密操作。运算数据都读取自内存。
② 在对加密或解密完成的数据进行写出的时候,都是将其直接写到指定好的文件,即直接写入磁盘。这是因为,考虑到中途可能因为意外断电等原因引起操作中断,为了保护已经花费时间运算完成的数据,将其直接写入磁盘。
③ 在关键算法上做进一步优化,例如在寻找素数时,素数测试使用更快速的算法;还有3.3节提到的,在用私有密钥进行幂模运算时使用中国余数定理等。
④ 对C++核心类库进行重点优化,使其运算效率尽可能提高。其中包括对各类之间的组织细节、各程序模块的具体编写等,进行全面细致的检查和修改,例如将大数据类型以对象指针传递而不拷贝,将简单的for循环展开等。
由于开发时间仓促等因素,在书写本文时,软件并未完成全面细致的优化。
3.3 使用中国余数定理
对于用RSA加密解密的一方,是计算。这里n=p×q,p和q是两个二进制长度接近的大素数。由于用私密密钥加密或解密的一方实际知道n的分解,即p和q,所以这一计算可以分解为以下两部分分别进行 (附录中有中国余数定理的简单介绍) 。

其中的C1、C2、d1、d2如下:

根据RSA算法的要求,私密密钥d的二进制长度接近n的长度,因此,d1和d2的二进制长度仅有n的一半左右,这样就节省了大量的计算工作。最后,应用中国余数定理就能计算出的值m。,其中.
如果应用中国余数定理计算幂模,主要工作花在计算上,计算C1、C2、d1、d2和m的运算与幂摸运算相比,计算时间较短可以不计。而位数对幂摸运算速度影响很大,因此分开计算比计算要快很多。经过测试,使用中国余数定理来简化一些幂模运算,速度比不使用中国余数定理时有很大的提高。
在书写本文时软件中尚未使用中国余数定理。
第4章 可移植模块的简要说明与开发前景
如2.1.2节所叙述,分层设计给移植带来方便,下面简要叙述各层可能的移植方式。
①实现RSA加密算法的C++核心类库,是基于C和C++的标准库创建的,没有用到C++泛型计算、STL相关内容,代码中没有任何与操作系统相关的内容。这是一种移植特性最好的程序模块,因为现今多数非PC设备支持C++编译。一般可以直接将本模块交叉编译给嵌入式设备,或在其他操作系统编译使用。在编译前,将rsa_san.cpp和vlong.cpp文件中的#include "stdafx.h"一行去掉,然后连同各自的头文件拷贝出来,仅这四个文件即为实现RSA加密算法的C++类库代码。例如,可以将它们在linux操作系统用gcc编译成程序模块,把RSA加密功能提供给系统上的其他程序使用。
②封装C++核心类库的DLL组件可以被Windows上的很多开发环境引用。例如在VB6,要使用这个组件,只需在程序最开始以引用win32api的方式引用即可,即public declare function XXX的形式。
③作为.Net类库,此模块可以被几十种支持.Net的语言引用。但是由于底层DLL组件的存在,使应用局限于PC上的Windows系统。在此层的使用不仅限于窗体应用程序,.Net类库可以由服务器程序(诸如aspx)方便的引用,以BS(浏览器服务器)的模式提供给网络上的用户使用,所以此应用程序可以通过简单的修改用于数字证书和数字签名等身份验证系统。
如1.2.2节最后所分析的,将任意文件加密成文本有其重要的意义。
因为此应用程序是可以将任意文件加密成文本的,所以加密成的数据可以方便的在Internet上传送。由此想到xml在Internet上携带数据的应用模式。实际上,此软件可以通过简单的修改实现将任意文件加密为一定格式的xml文件。通过这种加密方式,可以满足重要的小应用程序等小型二进制数据在网络上安全顺利传输的要求。而且,通过加密的数据以xml方式传送,使web应用的灵活性更好,此方式甚至可以看作一种通用的小型二进制数据安全交换协议来开发。
结束语
RSA应用于文件加密适合交流管理小型文件,将任意文件以非对称密钥加密成文本可以对其更方便的交流和管理,有广阔的开发前景。本项目应用的设计模式兼顾执行效率和可复用性。整个项目开放源代码和各种开发资料,便于引用和继续开发。应用本程序可以方便的在公众论坛等环境交流要求高度安全的各种数据,包括任意二进制和文本文件。
谢 辞
感谢蒋萍花老师的细心指导和各论坛程序员朋友的支持与建议。
参考文献
1.华罗庚,数论导引,科学出版社, 1979.
2.Montgomery PL, Modular multiplication without trialdivision[J], Mathematics of Computation, 1985, 44(170):519–521.
3.Oh JH,Moon S J, Modular multiplication method[J], IEE Proceedings:Computers and Digital Tech-niques, 1998, 145(4):317–318.
4.施向东 董平,基于RSA算法的一种新的加密核设计,微计算机信息 2005年第12-3期 中图分类号:TP289文章编号:1008-0570(2005)12-3-0039-03 页码39-41 < http://www.autocontrol.com.cn/magazine/3845.html >
5.[AX931] ANSI X9.31-1998 Digital Signatures using Reversible Public Key Cryptography for the Financial Services Industry (rDSA), Appendix A, American National Standards Institute, 1998.
6.[COCK73] Clifford Cocks. A Note on ‘Non-Secret Encryption’, CESG Research Report, 20 November 1973, < http://www.cesg.gov.uk/publications/media/nsecret/notense.pdf >.
7.[KALI93] Burton Kalinski. Some Examples of the PKCS Standards, RSA Laboratories, November 1993, < ftp://ftp.rsasecurity.com/pub/pkcs/ascii/examples.asc >.
8.[PKCS1] RSA Laboratories. PKCS #1 v2.1: RSA Encryption Standard. June 2002, < ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf >.
9.[RIVE78] R. Rivest, A. Shamir and L. Adleman. A Method for Obtaining Digital Signatures and Public-Key Cryptosystems.Communications of the ACM, 21 (2), pp. 120-126, February 1978.
10.陈发来,中国科学技术大学数学系 数学实验——素数 < http://www.teach.ustc.edu.cn/jpkc/guojia/sxsy/skja/exp5.ppt >

附 录
本项目源代码和已编译程序以及开发文档的下载地址
由于本软件代码量稍大,不方便全部附于本文,所以这里只提供下载地址。本软件已经在各下载站发布,如果下述连接下载过慢,可以Google搜索RSA Encrypter直接下载。本项目全部源代码也已经在VC代码库发布。
编译好的软件下载地址:
http://3mn.net/www/download/RSAEncrypter_dotnet11_20060121_sanPack.rar
源代码下载地址:
http://3mn.net/www/download/sanpack_rsa_vs2003sln_src.rar
相关开发资源站点:(提供各层接口函数和各类成员函数文档)
http://spdn.3mn.net/
软件使用帮助:
http://3mn.net/www/bbs/dispbbs.asp?boardID=7&ID=30&page=1

在C#使用Windows kernel32.dll中的API实现精确计时的类源代码
public class HighResolutionTimer
{
private long start;
private long stop;
private long frequency;
public HighResolutionTimer()
{
QueryPerformanceFrequency (ref frequency);
}
public void Start ()
{
QueryPerformanceCounter (ref start);
}
public void Stop ()
{
QueryPerformanceCounter (ref stop);
}
public float ElapsedTime
{
get
{
float elapsed = (((float)(stop - start)) / ((float) frequency));
return elapsed;
}
}
[System.Runtime.InteropServices.DllImport(“KERNEL32.dll”, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool QueryPerformanceCounter(ref long performanceCount);
[System.Runtime.InteropServices.DllImport(“KERNEL32.dll”, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool QueryPerformanceFrequency(ref long frequency);
}

加密解密测试时使用的两组密钥
密钥位数并不精确,可能有几位的差距。
512bit私有密钥(两行分别为d、n)
11C6EBA27BEA0A998C517D522DAE7ADA203F7325576C97853584C9253CD867B0FD6217579F8240F7FCB7474F1B532B8532794605C704D94513B240700BF04C9
195F9E4D3ABD729F6C7E7B7B6AF58DA89A10147DB6ADF0F4F3FA988E4C2441C2130C449852A68E19E32768FB3B41775DD4EF97F92674F3D21547249CD6D70C5
512bit公开密钥(两行分别为e、n)
C359
195F9E4D3ABD729F6C7E7B7B6AF58DA89A10147DB6ADF0F4F3FA988E4C2441C2130C449852A68E19E32768FB3B41775DD4EF97F92674F3D21547249CD6D70C5
1024bit私有密钥(两行分别为d、n)
79113601A430BCE489C6CBD825161014211DCC3D090D86C8F56E0F6324C0994A4E22596588C08B38354BA08C31DB6857BF919B8A67FDC0054A2C5BC783EEDEC3C1900A3AE66FAE1498562C6953FD9B7E0DD6EA515FE190D8123C31933328F79A5F1C63320499D09A4AC8F242E06F6BE903349570CE902197E6B24F3B8AB7D59
90640C3AA06DEC9ED00B8C232812B96F51979338282E782E8A10C5650D162781AC030747B0DEF22C42078036DDC6D42BC5728F5300CD6EDA1FFEC01D1F0B4FC06117BEC185E6429CA536D7BFB9B7235C6CC42A5C50C0C4798A04705212FC345170DEAB00A03E27D26292B7DE39F63874DC4FEB13DBE9C40B6DB9B593869EFD3
1024bit公开密钥(两行分别为e、n)
C359
90640C3AA06DEC9ED00B8C232812B96F51979338282E782E8A10C5650D162781AC030747B0DEF22C42078036DDC6D42BC5728F5300CD6EDA1FFEC01D1F0B4FC06117BEC185E6429CA536D7BFB9B7235C6CC42A5C50C0C4798A04705212FC345170DEAB00A03E27D26292B7DE39F63874DC4FEB13DBE9C40B6DB9B593869EFD3

RSA算法可行性的证明
求证:
<命题1-1> 若 p, q 是相异素数, e×d = 1 mod (p-1)×(q-1),
a 是任意一个正整数,
则有 c = a mod (p×q)
证明:
∵ d×e = 1 mod (p-1)×(q-1)
∴ d×e = k×(p-1)×(q-1) + 1, 其中 k 是整数
∵ 在 mod 中是 preserve 乘法的 (x = y mod z and u = v mod z  x×u = y×v mod z),

首先,素数p、q要么能整除a,要么与a互素。

  1. 如果 a 不是 p 的倍数, 也不是 q 的倍数时,
    则 (费马小定理)  (根据质数算术基本定理,a与素数p互素,则am也与p互素,m是整数)
    (费马小定理) 
    ∴ p, q 均能整除 - 1  p×q | - 1
    即 = 1 mod p×q
     c = = a mod p×q
  2. 如果 a 是 p 的倍数, 但不是 q 的倍数时,
    则 (费马小定理)
     = 1 mod q
     c = = a mod q
     q | c - a
    ∵ p | a
     c = = 0 mod p
     p | c - a
    ∴ p×q | c - a  c = a mod p×q
  3. 如果 a 是 q 的倍数,但不是 p 的倍数时,证明同2理显然
  4. 如果 a 同时是 p 和 q 的倍数时,
    则 p×q | a
     c = = 0 mod p×q
     p×q | c - a
     c = a mod p×q
    证毕□
    费马小定理叙述:e 是任一素数, n 是任一整数, 则 = n mod e (即如果 n 和 e 互质, 则 = 1 mod e) 运用群论知识可以证出费马小定理。
    命题1-1说明 a 经过编码为 b 再经过解码为 c 时, a = c mod n (n = p×q),但在做编码解码时, 由于限制 0 <= a < n, 0 <= c < n, 此时显然 a = c, 所以这个过程能做到编码解码的功能。

中国余数定理的简单介绍
令n=n1n2…nk,其中ni是两两互质的数,则对0<=a<n与0<=ai<ni且ai=a mod ni,a与(a1,a2…,ak)之间有一种一一对应的关系,一切对a的操作均可被等价的转换为对对应k元组中的每一元进行同样的操作。因此我们可以将一种表达经过简单的转换后得出另一种表达,其中从a到(a1,a2…,ak)的转换十分容易,而从(a1,a2…,ak)推得对应的a则要稍微复杂一些。
首先定义mi=n/ni(i=1,2…k),则mi是除了ni以外的所有nj的乘积,接下来令ci=mi与模n意义下mi的逆元的积,则a为(a1c1+a2c2+…+akck) (mod n)。
例如,已知a模5余2 且 模13余3,那么a1=2,n1=m2=5,a2=3,n2=m1=13,则有c1=13*(2 mod 5)=26,c2=5*(8 mod 13)=40,所以a=(226+340)(mod 65)=42。

猜你喜欢

转载自blog.csdn.net/ambiguous__/article/details/130776646