RSA是最流行的非对称加密算法之一。也被称为公钥加密。它是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一块儿提出的。当时他们三人都在麻省理工学院工做。RSA就是他们三人姓氏开头字母拼在一块儿组成的。算法
RSA是非对称的,也就是用来加密的密钥和用来解密的密钥不是同一个。安全
和DES同样的是,RSA也是分组加密算法,不一样的是分组大小能够根据密钥的大小而改变。若是加密的数据不是分组大小的整数倍,则会根据具体的应用方式增长额外的填充位。网络
RSA做为一种非对称的加密算法,其中很重要的一特色是当数据在网络中传输时,用来加密数据的密钥并不须要也和数据一块儿传送。所以,这就减小了密钥泄露的可能性。RSA在不容许加密方解密数据时也颇有用,加密的一方使用一个密钥,称为公钥,解密的一方使用另外一个密钥,称为私钥,私钥须要保持其私有性。数据结构
RSA被认为是很是安全的,不过计算速度要比DES慢不少。同DES同样,其安全性也从未被证实过,但想攻破RSA算法涉及的大数(至少200位的大数)的因子分解是一个极其困难的问题。因此,因为缺少解决大数的因子分解的有效方法,所以,能够推测出目前没有有效的办法能够破解RSA。函数
RSA算法基于的原理,基本上来讲,加密和解密数据围绕着模幂运算,这是取模计算中的一种。取模计算是整数计算中的一种常见形式。x mod n的结果就是x / n的余数。好比,40 mod 13 = 1,由于40 / 13 = 3,余数为1。模幂运算就是计算ab mod n的过程。优化
RSA中的公钥和私钥须要结合在一块儿工做。公钥用来对数据块加密,以后 ,只有对应的私钥才能用来解密。生成密钥时,须要遵循几个步骤以确保公钥和私钥的这种关系可以正常工做。这些步骤也确保没有实际方法可以从一个密钥推出另外一个。加密
开始前,首先要选择两个大的素数,记为p和q。根据当今求解大数因子的技术水平,这两个数应该至少有200位,这们在实践中才能够认为是安全的。spa
而后,开始计算n:code
n = pqblog
接下来,选择一个小的奇数e,它将成为公钥的一部分。选择e最须要考虑的重点是它与(p-1)(q-1)不能有相同的因子。换句话说,e与(p-1)(q-1)是互为素数关系的。好比,若是p=11而q=19,那么n=11 X 19=209。这里选择e=17,由于(p-1)(q-1)=10 X 18 =180,而17和180没有相同的因子。一般选择三、1七、6五、537做为e的值。使用这些值不会对RSA的安全性形成影响,由于解密数据还须要用到私钥。
一旦为e选择了一个值,接下来开始计算相对应的值d,d将成为私钥的一部分。d的值就是计算e的倒数对(p-1)(q-1)的取模结果,公式以下:
d = e-1 mod (p-1)(q-1)
这里d和e是模乘法逆元的关系。
思考一下这个问题:当d为多少时能够知足ed mod (p-1)(q-1) = 1 ?好比在等式 17d mod 180 = 1中,d的一个可能值是53。其余的可能值是23三、41三、593等。在实践中,能够利用欧几里德算法来计算模乘法逆元。这里就再也不展开。
如今有了e和d的值,将(e,n)做为公钥P,将(d,n)做为私钥S并保持其不可见。表示为:
P = (e,n) , S = (d,n)
加密方使用P来加密数据,解密方使用S来解密。为了防止就算有人知道了P也没法推算出S,必须保证p和q的值绝对不能暴露。
P和S结合在一块儿提供的安全性来自于一个事实,那就是乘法是一种很好的单向函数。
单向函数是加密技术的基础。简单的说,单向函数就是在一个方向上可以很容易算出结果,但反向推导则是不切实际的。好比,在RSA算法中,计算p和q的成绩是一种单向函数,由于尽管计算p和q的成绩很容易,但将n反向因子分解为p和q则是极其耗时的。这里,选择的p和q的值要足够大才能够。
计算P和S的步骤起源于欧拉函数中的一些有趣性质。特别是,这些性质容许对模幂运算作一些有用的操做。
欧拉函数记为φ(n),定义全部小于n的正整数里和n互素的整数的个数。
只有当两个整数的惟一公因子为1时,才说这两个整数是互素的。例如,φ(8)=4,由于一共只用4个比8小的整数是互素的,它们是1,3,5,7。
欧拉方程有两个性质对RSA算法来讲是特别重要的。
第一,当n是素数时,φ(n)=n-1。这是因为n的惟一因子是1和n,所以,n与以前的全部n-1个正整数都是互素的。
另外一个有趣的性质是对于任意小于n且与n互素的正整数a,都有aφ(n) mod n = 1。例如,14 mod 8 = 1, 34 mod 8 = 1, 54 mod 8 = 1, 74 mod 8 = 1。对上述方程两边都乘以a,获得:
(a)(aφ(n) mod n)=a,或者aφ(n)+1 mod n = a
所以,能够获得15 mod 8 = 1, 35 mod 8 = 3, 55 mod 8 = 5, 75 mod 8 = 7。
调整以后获得的等式很是强大。由于对于某些等式c = me mod n,该等于可让咱们找出一个d的值,使得cd mod n = m。
这就是RSA算法中容许加密数据,以后再解密回原文的恒等式。能够按照以下方式表示:
cd mod n = (me)d mod n = med mod n = mφ(n)+1 mod n = m mod n
欧拉函数和指数间的关系保证了加密的任意数据都可以惟一地解密回来。为了找出d的值,解方程d = e-1 φ(n) +1。不巧的是,对于方程d = e-1φ(n)+1不必定老是有整数解。为了解决这种问题,转而计算
d mod φ(n)的值。换句话说,d = (e-1 φ(n) + 1) mod φ(n),能够简化为:
d = e-1 mod φ(n)
咱们能够获得这样的简化形式,由于(φ(n)+1) mod φ(n) = (φ(n)+1) - φ(n) = 1。能够用任意的正整数替代φ(n)来证实等式成立。注意这个方程式同以前计算密钥的过程当中得出d的推导式之间的类似之处。这提供了一种经过e和n来计算d的方法。固然了,e和n是公开的,对于攻击者来讲是事先可知的,所以就有人问,这难道不是给了攻击者相同的机会来计算出私钥吗?讨论到这里,是时候来探讨一下RSA算法安全性保障的由来了。
RSA算法的安全性保障来自一个重要的事实,那就是欧拉函数是乘法性质的。这意味着若是p和q是互素的,那么有φ(pq)=φ(p)φ(q)。所以,若是有两个素数p和q,且n=p*q,则φ(n)=(p-1)(q-1),并且最重要的是:
d = e-1 mod (p-1)(q-1)
所以,尽管攻击者可能知道了e和n的值,为了计算出d必须知道φ(n),而这又必须同时获得p和q的值才能办到。因为p和q是不可知的,所以攻击者只能计算n的因子,只要给出的p和q的值足够大,这就是一个至关耗费时间的过程。
要使用RSA算法对数据进行加密和解密,首先要肯定分组的大小。为了实现这一步,必须确保该分组能够保存的最大数值要小于n的位数。好比,若是p和q都是200位数字的素数,则n的结果将小于400位。于是,所选择的分组所能保存的最大值应该要以是接近于400。在实践中,一般选择的位数都是比n小的2的整数次幂。好比,若是n是209,要选择的分组大小就是7位,由于27 = 128比209小,但28 = 256又大于209。
要从缓冲区M中加密第(i)组明文Mi ,使用公钥(e,n)来获取M的数值,对其求e次幂,而后再对n取模。这将产生一组密文Ci。对n的取模操做确保了Ci将和明文的分组大小保持一致。于是,要加密明文分组有:
Ci = Mie mod n
以前提到过,欧拉函数是采用幂模运算来加密数据的基础,根据欧拉函数及其推导式,可以将密文解密回原文。
要对缓冲区中C中的第(i)组密文进行解密,使用私钥(d,n)来获取Ci的数值部分,对其求d次幂,而后再对n取模。这将产生一组明文Mi。所以,要解密密文分组有:
Mi = Cid mod n
rsa_encipher
void rsa_encipher(Huge plaintext, Huge *ciphertext, RsaPubKey pubkey);
返回值:无
描述:采用RSA算法来加密由plaintext所指定的明文分组。
pubkey是所指定的公钥(e,n),其类型为结构体RsaPubKey。
ciphertext是返回的同plaintext一样大小的密文分组。由调用者负责管理ciphertext所关联的存储空间。要加密大段的数据,能够按照前面介绍的分组加密模式来调用rsa_encipher。
复杂度:O(1)
rsa_decipher
void rsa_decipher(Huge ciphertext, Huge *plaintext, RsaPriKey prikey)
返回值:无
描述:采用RSA算法来解密由ciphertext所指定的密文分组。
prikey是所指定的私钥(d,n),其类型为结构体RsaPriKey。
plaintext是返回的同ciphertext一样大小的明文分组。由调用者负责管理plaintext所关联的存储空间。要解密大段的数据,能够按照前面介绍的分组加密模式来调用rsa_decipher。
复杂度:O(1)
由于RSA加密算法须要的只不过是计算ab mod n,因此基本的实现是比较简单的。关键的是计算幂模的函数。
可是,要使RSA的安全性获得保障,必须使用很大的整数,这就使得事情变得复杂了。特别是,全部的计算中使用到的整数位数必须是密钥位数的2倍(稍后将在幂模计算中看到)。所以,若是密钥是一个200位的整数,就须要一个抽象数据类型来支持至少400位的整数。
关于大数运算已经有一些可用的函数库。这里再也不提供具体的实现。在数据加密头文件的示例中,定义了数据类型Huge,在安全的实现中,能够为Huge类型指定typedef别名以支持所选择的大整数抽象数据类型。其余的需求就只剩下替换整数计算中的运算符为Huge类型所支持的操做。为了达到说明的目的,在这里的实现中Huge类型用typedef定义为unsigned long,这种C语言内置的类型一般只能提供10位十进制数的支持。这意味着稍后的实现示例给出的实现只能支持最多5位整数的密钥。所以,虽然示例中的实现是可用的,但若是不把Huge重定义为大数类型,这个实现就不是安全的。
rsa_encipher函数采用RSA算法将明文分组加密。
经过调用函数modexp来计算ab mod n的值,这里a表明明文分组,b和n表明公钥的e和n成员。为了提升执行效率,modexp使用称为二进制平方-乘的算法来计算模幂。
二进制平方-乘算法避免了当a和b都很大时计算ab时会出现的超大中间值。好比,假设当a、b和n都是包含200位数字的超大整数,计算ab mod n,其结果是一个40 000位的整数对200位的整数取模!由于最终获得的结果就是一个200位的整数,那么这里的目的就是要避免出现40 000位的整数的中间值。
用二进制平方-乘算法计算ab mod n,主要是多个开平方运算的结果(以下图)。首先,将b表示为二进制形式,而后从右边的位开始处理。对于b中的每一位,求出a的平方对n取模的结果,并将这个结果赋给a。每当遇到b中为1的位时,就将当前的a值乘上另外一个寄存器y(初始值为1),并将结果再赋给y。一旦迭代至b的最高有效位,y的值就是ab mod n的结果。在整个计算过程当中,产生的最大值是a2。所以,若是a是一个包含200位数字的整数,就不用处理大于400位的数字了。这相对于前面提到过的包含40 000位数字的整数来讲已是很大的优化了。下图中的阴影部分展现了计算511 mod 53 = 48 828 125 mod 53 = 20的过程。在这个计算过程当中,相比511 = 48 828 125,所处理的最大数值只有422 = 1764。
图示:采用二进制平方-乘算法来计算模幂
rsa_encipher的时间复杂度为O(1),由于加密一个明文分组的全部步骤都能在恒定的时间内完成。因为分组的大小是固定的,所以modexp中的循环执行时间也是恒定的。
rsa_decipher函数采用RSA算法将密文分组进行解密。
该操做经过调用modexp来解密。modexp计算ab mod n的结果,这里a是密文分组,b和n表明私钥成员d和n。处理过程同rsa_decipher中描述的同样。
rsa_decipher的时间复杂度为O(1),由于解密密文分组的全部步骤均可以在恒定的时间内完成。因为分组大小固定,所以,modexp中的循环执行时间也是恒定的。
示例:数据加密的头文件代码
/*encrypt.h*/ #ifndef ENCRYPT_H #define ENCRYPT_H /*在一个安全实现中,Huge 最少要400位10进制数字*/ typedef unsigned long Huge; /*为RSA公钥定义一个数据结构*/ typedef struct RsaPubKey_ { Huge e; Huge n; }RsaPubkey; /*为RSA私钥定义一个数据结构*/ typedef struct RsaPriKey_ { Huge d; Huge n; }RsaPriKey; /*函数声明*/ void des_encipher(const unsigned char *plaintext, unsigned char *ciphertext, const unsigned char *key); void des_decipher(const unsigned char *ciphertext, unsigned char *plaintext, const unsigned char *key); void rsa_encipher(Huge plaintext, Huge *ciphertext, RsaPubKey pubkey); void rsa_decipher(Huge ciphertext,Huge *plaintext, RsaPriKey prikey); #endif // ENCRYPT_H
示例:RSA算法的实现
/*rsa.c RSA算法实现*/ #include "encrypt.h" /*modexp 二进制平方乘算法函数*/ static Huge modexp(Huge a, Huge b, Huge n) { Huge y; /*使用二进制平方乘法计算 pow(a,b) % n*/ y=1; while(b != 0) { /*对于b中的每一个1,累加y*/ if(b & 1) y = (y*a) % n; /*对于b中的每一位,计算a的平方*/ a = (a*a) % n; /*准备b中的下一位*/ b = b>>1; } return y; } /*rsa_encipher RSA算法加密*/ void rsa_encipher(Huge plaintext, Huge *ciphertext, RsaPubKey pubkey) { *ciphertext = modexp(plaintext, pubkey.e, pubkey.n); return; } /*rsa_decipher RSA算法解密*/ void rsa_decipher(Huge ciphertext, Huge *plaintext, RsaPriKey prikey) { *plaintext = modexp(ciphertext, prikey.d, prikdy.n); return; }