加密算法 您所在的位置:网站首页 rsa加密使用 加密算法

加密算法

2023-03-10 10:23| 来源: 网络整理| 查看: 265

RSA算法原理转自:https://www.cnblogs.com/idreamo/p/9411265.html

C++代码实现部分为本文新加

 

 

RSA算法简介

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位,这们在实践中才可以认为是安全的。

然后,开始计算n:

n = pq

接下来,选择一个小的奇数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没有相同的因子。通常选择3、17、65、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。其他的可能值是233、413、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的接口定义

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算法的实现与分析

因为RSA加密算法需要的只不过是计算ab mod n,所以基本的实现是比较简单的。关键的是计算幂模的函数。

但是,要使RSA的安全性得到保障,必须使用很大的整数,这就使得事情变得复杂了。特别是,所有的计算中使用到的整数位数必须是密钥位数的2倍(稍后将在幂模计算中看到)。因此,如果密钥是一个200位的整数,就需要一个抽象数据类型来支持至少400位的整数。

关于大数运算已经有一些可用的函数库。这里不再提供具体的实现。在数据加密头文件的示例中,定义了数据类型Huge,在安全的实现中,可以为Huge类型指定typedef别名以支持所选择的大整数抽象数据类型。其他的需求就只剩下替换整数计算中的运算符为Huge类型所支持的操作。为了达到说明的目的,在这里的实现中Huge类型用typedef定义为unsigned long,这种C语言内置的类型通常只能提供10位十进制数的支持。这意味着稍后的实现示例给出的实现只能支持最多5位整数的密钥。因此,虽然示例中的实现是可用的,但如果不把Huge重定义为大数类型,这个实现就不是安全的。

rsa_encipher

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_decipher函数采用RSA算法将密文分组进行解密。

该操作通过调用modexp来解密。modexp计算ab mod n的结果,这里a是密文分组,b和n代表私钥成员d和n。处理过程同rsa_decipher中描述的一样。

rsa_decipher的时间复杂度为O(1),因为解密密文分组的所有步骤都可以在恒定的时间内完成。由于分组大小固定,因此,modexp中的循环执行时间也是恒定的。

 

C++代码实现:

下面代码只是rsa加密算法的简单实现,其并未涉及大数处理(根据上面的介绍我们已经得知只有数位足够大才能保证安全性,所以此代码并不完整)

此代码下方为涉及大数的完整RSA代码

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 using namespace std; 9 typedef long long ll; 10 int e, d, n; 11 12 int gcd(int a, int b) //求最大公约数 13 { 14 int c = 0; 15 if(a1; 62 } 63 64 return y; 65 // complete this part 66 } 67 68 void extgcd(ll a,ll b,ll& d,ll& x,ll& y) //获取(1/a)modb得结果 69 { 70 if(!b) 71 { 72 d=a; 73 x=1; 74 y=0; 75 } 76 else 77 { 78 extgcd(b,a%b,d,y,x); 79 y-=x*(a/b); 80 } 81 } 82 83 int ModularInverse(int a,int b) //获取(1/a)modb得结果 84 { 85 ll d,x,y; 86 extgcd(a,b,d,x,y); 87 return d==1?(x+b)%b:-1; 88 // complete this part 89 } 90 91 void KeyGeneration() //获取公钥密钥 92 { 93 int p, q; 94 int phi_n; 95 96 do 97 { 98 do 99 p = rand(); 100 while (p % 2 == 0); 101 102 } 103 while (!PrimarityTest(2, p)); 104 105 do 106 { 107 do 108 q = rand(); 109 while (q % 2 == 0); 110 } 111 while (!PrimarityTest(2, q)); 112 113 n = p * q; 114 phi_n = (p - 1) * (q - 1); 115 116 do 117 e = rand() % (phi_n - 2) + 2; // 1 < e < phi_n 118 while (gcd(e, phi_n) != 1); 119 120 d = ModularInverse(e,phi_n); 121 } 122 123 void Encryption(int value, FILE* out) //加密 124 { 125 int cipher; 126 cipher = ModularExponention(value, e, n); 127 fprintf(out, "%d ", cipher); 128 } 129 130 void Decryption(int value, FILE* out) //解密 131 { 132 int decipher; 133 decipher = ModularExponention(value, d, n); 134 fprintf(out, "%c", decipher); 135 } 136 int main(void) 137 { 138 FILE* inp, * out; 139 char filepath[15], filename[100]; 140 141 strcpy(filepath, "F:\Desktop\\"); //文件路径 142 143 sprintf(filename, "%s%s", filepath, "cipher.txt"); 144 out = fopen(filename, "w+"); //打开文件 145 fclose(out); 146 sprintf(filename, "%s%s", filepath, "decipher.txt"); 147 out = fopen(filename, "w+"); //打开文件 148 fclose(out); 149 150 KeyGeneration(); //获取公钥密钥 151 152 sprintf(filename, "%s%s", filepath, "plain.txt"); 153 inp = fopen(filename, "r+"); //读取原文件 154 if (inp == NULL) 155 { 156 printf("Error opening Source File.\n"); 157 exit(1); 158 } 159 160 sprintf(filename, "%s%s", filepath, "cipher.txt"); 161 out = fopen(filename, "w+"); 162 if (out == NULL) 163 { 164 printf("Error opening Destination File.\n"); 165 exit(1); 166 } 167 168 // encryption starts 169 while (1) 170 { 171 char ch = getc(inp); //读取文件字符,一个字符一个字符的读取输出 172 if (ch == -1) 173 break; 174 int value = toascii(ch); //toascii将字符转化成对应ascall值 175 Encryption(value, out); //加密输出 176 } 177 178 fclose(inp); 179 fclose(out); 180 181 // decryption starts 182 sprintf(filename, "%s%s", filepath, "cipher.txt"); 183 inp = fopen(filename, "r"); 184 if (inp == NULL) 185 { 186 printf("Error opening Cipher Text.\n"); 187 exit(1); 188 } 189 190 sprintf(filename, "%s%s", filepath, "decipher.txt"); 191 out = fopen(filename, "w+"); 192 if (out == NULL) 193 { 194 printf("Error opening File.\n"); 195 exit(1); 196 } 197 198 while (1) 199 { 200 int cip; 201 if (fscanf(inp, "%d", &cip) == -1) 202 break; 203 Decryption(cip, out); //解密 204 } 205 fclose(out); 206 207 return 0; 208 }

 

以下内容参考自:https://blog.csdn.net/qmickecs/article/details/39676655

 

RSA大数所用库:

GMP简介 GMP(the GNU Multiple Precision arithmetic library)是著名的任意精度算术运算库,支持任意精度的整数、有理数以及浮点数的四则运算、求模、求幂、开方等基本运算,还支持部分数论相关运算。Maple、Mathematica等大型数学软件的高精度算术运算功能都是利用GMP实现的。   RSA代码:

GMP是一个基于C语言的开源库,其中包含了数种自定义数据类型,包括

mpz_t 多精度整型 mpq_t 多精度有理数 mpf_t 多精度浮点型

GMP要求一个mpz_t类型变量在被使用前必须手动进行初始化,并且不允许对已经初始化的变量进行初始化。  

下面是一些本文中使用到的部分函数,其他函数介绍以及用法请参考GMP官方文档:

1 mpz_t x 2 声明一个多精度整型变量x 3 4 void mpz_init (mpz_t x) 5 初始化x。任何一个mpz_t类型的变量在使用前都应该初始化。 6 7 void mpz_init_set_ui (mpz_t rop, unsigned long int op) 8 初始化rop,并将其值设置为op 9 10 int mpz_init_set_str (mpz_t rop, const char *str, int base) 11 初始化rop,并赋值rop = str,其中str是一个表示base进制整数的字符数组 12 13 void mpz_clear (mpz_t x) 14 释放x所占用的内存空间 15 16 void mpz_sub_ui (mpz_t rop, const mpz_t op1, unsigned long int op2) 17 计算op1 – op2,结果保存在rop中 18 19 void mpz_mul (mpz_t rop, const mpz_t op1, const mpz_t op2) 20 计算op1 * op2,结果保存在rop中 21 22 void gmp_randinit_default (gmp_randstate_t state) 23 设置state的随机数生成算法,默认为梅森旋转算法 24 25 void gmp_randseed_ui (gmp_randstate_t state, unsigned long int seed) 26 设置state的随机化种子为seed 27 28 void mpz_urandomb (mpz_t rop, gmp_randstate_t state, mp_bitcnt_t n) 29 根据state生成一个在范围0~2^n-1内均匀分布的整数,结果保存在rop中 30 31 char * mpz_get_str (char *str, int base, const mpz_t op) 32 将op以base进制的形式保存到字符数组中,该函数要求指针str为NULL(GMP会自动为其分配合适的空间),或者所指向的数组拥有足够存放op的空间 33 34 int gmp_printf (const char *fmt, ...) 35 语法跟C语言中的标准输出函数printf类似。它在printf的基础上,增加了mpz_t等数据类型的格式化输出功能。fmt为输出格式,例如fmt=”%Zd”时,表示输出一个10进制的多精度整型。其后的所有参数为输出的内容。 36 37 int mpz_probab_prime_p (const mpz_t n, int reps) 38 检测n是否为素数。该函数首先对n进行试除,然后使用米勒-拉宾素性检测对n进行测试,reps表示进行检测的次数。如果n为素数,返回2;如果n可能为素数,返回1;如果n为合数,返回0。

代码:

1 #include 2 3 #include 4 5 #include 6 7 #include 8 9 #include 10 11 #include 12 13 14 15 #define KEY_LENGTH 2048 //公钥的长度 16 17 #define BASE 16 //输入输出的数字进制 18 19 20 21 using namespace std; 22 23 24 25 struct key_pair 26 27 { 28 29 char * n; 30 31 char * d; 32 33 int e; 34 35 }; 36 37 38 39 //生成两个大素数 40 41 mpz_t * gen_primes() 42 43 { 44 45 gmp_randstate_t grt; 46 47 gmp_randinit_default(grt); 48 49 gmp_randseed_ui(grt, time(NULL)); 50 51 52 53 mpz_t key_p, key_q; 54 55 mpz_init(key_p); 56 57 mpz_init(key_q); 58 59 60 61 mpz_urandomb(key_p, grt, KEY_LENGTH / 2); 62 63 mpz_urandomb(key_q, grt, KEY_LENGTH / 2); //随机生成两个大整数 64 65 66 67 mpz_t * result = new mpz_t[2]; 68 69 mpz_init(result[0]); 70 71 mpz_init(result[1]); 72 73 74 75 mpz_nextprime(result[0], key_p); //使用GMP自带的素数生成函数 76 77 mpz_nextprime(result[1], key_q); 78 79 80 81 mpz_clear(key_p); 82 83 mpz_clear(key_q); 84 85 86 87 return result; 88 89 } 90 91 92 93 //生成密钥对 94 95 key_pair * gen_key_pair() 96 97 { 98 99 mpz_t * primes = gen_primes(); 100 101 102 103 mpz_t key_n, key_e, key_f; 104 105 mpz_init(key_n); 106 107 mpz_init(key_f); 108 109 mpz_init_set_ui(key_e, 65537); //设置e为65537 110 111 112 113 mpz_mul(key_n, primes[0], primes[1]); //计算n=p*q 114 115 mpz_sub_ui(primes[0], primes[0], 1); //p=p-1 116 117 mpz_sub_ui(primes[1], primes[1], 1); //q=q-1 118 119 mpz_mul(key_f, primes[0], primes[1]); //计算欧拉函数φ(n)=(p-1)*(q-1) 120 121 122 123 mpz_t key_d; 124 125 mpz_init(key_d); 126 127 mpz_invert(key_d, key_e, key_f); //计算数论倒数 128 129 130 131 key_pair * result = new key_pair; 132 133 134 135 char * buf_n = new char[KEY_LENGTH + 10]; 136 137 char * buf_d = new char[KEY_LENGTH + 10]; 138 139 140 141 mpz_get_str(buf_n, BASE, key_n); 142 143 result->n = buf_n; 144 145 mpz_get_str(buf_d, BASE, key_d); 146 147 result->d = buf_d; 148 149 result->e = 65537; 150 151 152 153 mpz_clear(primes[0]); //释放内存 154 155 mpz_clear(primes[1]); 156 157 mpz_clear(key_n); 158 159 mpz_clear(key_d); 160 161 mpz_clear(key_e); 162 163 mpz_clear(key_f); 164 165 delete []primes; 166 167 168 169 return result; 170 171 } 172 173 174 175 //加密函数 176 177 char * encrypt(const char * plain_text, const char * key_n, int key_e) 178 179 { 180 181 mpz_t M, C, n; 182 183 mpz_init_set_str(M, plain_text, BASE); 184 185 mpz_init_set_str(n, key_n, BASE); 186 187 mpz_init_set_ui(C, 0); 188 189 190 191 mpz_powm_ui(C, M, key_e, n); //使用GMP中模幂计算函数 192 193 194 195 char * result = new char[KEY_LENGTH + 10]; 196 197 mpz_get_str(result, BASE, C); 198 199 200 201 return result; 202 203 } 204 205 206 207 //解密函数 208 209 char * decrypt(const char * cipher_text, const char * key_n, const char * key_d) 210 211 { 212 213 mpz_t M, C, n, d; 214 215 mpz_init_set_str(C, cipher_text, BASE); 216 217 mpz_init_set_str(n, key_n, BASE); 218 219 mpz_init_set_str(d, key_d, BASE); 220 221 mpz_init(M); 222 223 224 225 mpz_powm(M, C, d, n); //使用GMP中的模幂计算函数 226 227 228 229 char * result = new char[KEY_LENGTH + 10]; 230 231 mpz_get_str(result, BASE, M); 232 233 234 235 return result; 236 237 } 238 239 240 241 int main() 242 243 { 244 245 key_pair * p = gen_key_pair(); 246 247 248 249 cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有