双钥密钥密码算法RSA解析与短密钥实现

来源:互联网 发布:当今网络世界强国 编辑:程序博客网 时间:2024/05/22 09:40

单钥密钥算法,由于其加密的速度相对来说比较快,所以常用来对文本文件加密(如TEA、DES等),而双钥密钥算法(如RSA)由于其加密解密的密钥不同并且采用暴力破解的方式也比较低效(基本不可能被破解),低效的原因通常不是符合加解密的密钥对的空间有多大,而是正确的一对密钥其密钥空间难以确定。但是其加密速度也比较低,所以常用来加密单钥密钥算法的加密秘钥,这样即所谓混合加密。混合密码系统的基本结构如下图所述(解密过程与加密过程相反,并且解密用的公钥密码是接受者的私钥,加密类似于转码组包,解密类似于拆包转码):

这里写图片描述

对于混合加密系统的左半部分,我们以RSA为例来进行研究。

1、非对称密码与RSA密码算法简介:

RSA是三位开发者Ron Rivest、Adi Shamir和Leonard Adleman的姓氏首字母所组成的。RSA从框架结构理解上来说算法比较简单,为什么简单?因为它的加密解密只需要两个公式(其实是一个公式):

Encryption:
Ciphertext = Plaintext^E mod N(明文Plaintext的E次方对N取余的值为密文)
Decryption:
Plaintext = Ciphertext^D mod N(密文Ciphertext的D次方对N取余的值为明文)

其中{E,N}是加密秘钥,也称为公钥(因为该密钥是不需要可以保密的)。而{D,N}是解密密钥,也称为私钥,是需要保密的。A产生一对密钥K11、K12(公钥和私钥),将公钥K11发送给需要进行通信的发送一方S,私钥K12自己保管。即使公钥被窃听者E获取到,由于没有私钥,窃听者也不能的值通信信息。根据描述,我们可以很清楚地知道,一对密钥只能进行单工通信,不是因为信道做不到双工通信,而是信道达不到安全的双工通信。那么就需要另一方S也产生一对密钥K21、K22,将公钥K21发送A,私钥K22自己保留。则A要S通信只能用K21加密信息给S发送,而该信息只有S能用K22解密。S和A通信也只能用K11加密后给A发送信息,而该信息只能由A用私钥K12解密读取。

看起来比较混乱,用下图来表示:
这里写图片描述

2、RSA密码算法需要的数论知识:

而加密公式与解密公式中,并不是所有的{E,N}、{D,N}组合都能够组成一对密钥。E、D、N是具有一定的关系的,该组合由数学关系推导出来。关于如何求解一组密钥,后面在慢慢分析。我们先来回顾和学习,与RSA加密解密密钥组合生成相关的一些数学知识:

(1)、质数(素数)的相关概念:

①对于整数Number,只能分解为Number = Number * 1,而不能分解为其它整数相乘,则Number为质数(素数);
②两个质数的乘积是这两个质数的最小公倍数;
③**互质的概念:**A与B互质,并不是说A、B均为质数,而是指A和B的最大公约数是1;如:8和q最大公约数为1,但是8和9都是合数。
④任何大于1的整数Number,能够被分解为如下形式:
Number = P1 * P2 * P3 * … * Pi,(P1 、P2、P3、… 、Pi均为质数)

(2)、mod运算(取模运算):

①取模(mod)与取余(rem):A mod B(A对B取模)、A rem B(A对B取余)
它们返回结果都是余数,但rem和mod唯一的区别在于:
当A、B同号时,两个运算结果是等同的;
当A、B异号时,rem结果的符号和A的符号一样,而mod和B的符号一样。
区分详细可参考:取模运算(mod)和求余(rem)运算不能被混为一谈
②((a mod n) × (b mod n))mod n ≡ (a×b) mod n
如果(a×b)=(a×c) mod n,且a与n互质,则b = c mod n

(3)、费马定理:

如果p是质数,a与p互质,则由a^(p-1) mod p = 1

(4)、欧拉定理:

欧拉函数ψ(n)表示不大于n且与n互质的正整数个数。当n为质数时,ψ(n)=n-1;当n=p * q,(p、q均为质数)时,ψ(n)=ψ(p) * ψ(q)=(p-1) * (q-1)

3、RSA密码算法的流程:

RSA密钥的产生,就需要用到以上的数论知识,我们来具体了解一下RSA密钥(N、E、D)的产生步骤:

(1)、产生N:

N = p * q,p和q是两个非常大的质数(一般来说为512bit,十进制为200位左右),大是因为为了质数的组合空间大,以保证选定的p和q不被轻易“猜出来”。p和q要是被知道了,则密码的破译工作就简单多了。

(2)、产生E:

E、p、q满足两个关系:
1 < E < lcm((p-1) , (q-1)),其中L = lcm((p-1) , (q-1))是p-1和q-1的最小公倍数(least common multiple)
gcd(E,L)=1,gcd(E,L)为E和L的最大公约数,即E和 lcm((p-1) , (q-1))要互质(但E不一定为质数)。
由上面两个关系,或者说方程求解出来的E一般不止一个,而我们要用于RSA的E必须是从满足方程的E集合中随机取出的一个。

(3)、产生D:

D、E、L必须满足下列关系式:
1 < D < L,这点和E是一样的
E × D mod L = 1,上面提到E和L的最大公约数为1的目的就是为了让该方程有解。如果无解,就不存在前面分析时的K12和K22了,而全部是K??。
关于为什么,当E和L最大公约数是1时,D才会有解的问题,可以参考《图解密码技术》日,结城浩著的第一版:5.5时钟运算。

以上算是密钥对{E,N}、{D,N}的产生(我们只需要保存{D,N}不让其他人知道,并将加密程序和{E,N}公钥发送给需要进行通信的一方,就可以让对方的明文加密后只有自己能解密),而实际的加密解密就只有这两个公式:
Ciphertext = Plaintext^E mod N
Plaintext = Ciphertext^D mod N

注意:由于公式中存在次幂运算,而底数和幂数都十分大,所以数字的计算量十分庞大,虽然数字越大越安全(p、q越大,得到的N越大,破解时根据N来反推p、q空间的难度越大,毕竟到目前为止没有一个有效的办法进行N的分解因式),但是这也就意味着加密效率十分低下,而一般我们用混合加密只用RSA加密单钥密钥的密钥(几十到几百bit,比较小)。而不是加密明文信息。所以效率问题可以接受。

4、从algorithm到code的分析与实现:

实现真正的RSA比较麻烦,因为数字比较大,不能用普通的计算机数据类型来简单表示,而需要大数运算作为支撑。所以这里我们只进行小的质数p、q的密钥产生,以unsigned long作为“大数类型huge_t”。

我们具体分析的算法为,如何对一个大数的大数次幂再对大数取模的式子进行运算?比如99^111 mod 143。99的5次方就已经超过unsigned long的表示范围了,要计算99^111次方先不说效率问题,仅仅是结果溢出丢失真实数据就不能采用该方法,并且该方法的效率也是一个天大的问题,当A^B mod N的A、B、N都是200位整数时,效率问题更为突出。而解决方法为:二进制平方乘算法

所谓二进制平方乘算法,就是根据依次降幂(二进制从高到低向右移位)对b的每一位进行操作。对于:a^b mod n,如何做呢?首先将b表示成二进制形式(本身就是二进制形式,操作用位运算即可),然后从最右边的位(bit)开始处理。对于b中的每个位,求出a的平方对n取模的结果并将这个结果赋给a。当遇到b中为1的bit时,就用当前a值乘以另外一个值y(初始值为1)对n取模的结果赋给y。一旦迭代至b的最高有效位,y的值就是a^b mod n的结果。
代码描述如下:

//a为输入的信息(明文或密文)、b为幂数(e或d)、n为模数//返回值为加密或解密后的密文或明文const huge_t ModExp(huge_t a, huge_t b, huge_t n){    huge_t y =1;//初始值为1    while(b != 0){//未至b最高位        if(b & 0X01){//如果该bit为1            y = (y * a) % n;//用当前a值乘以另外一个值y,赋给y        }        a = (a * a) % n;        b = b >> 1;    }    return y;}

整个计算过程中产生的最大值是a²,而不像原来的方法中最大值是a^b,小了不止一点。并且当数字为unsigned long类型时,循环只需要执行32次,而不是b次(b可能为上亿次,b为大数时则更是“天文”次循环)。效率提高的也不是亿万次可以形容的了。

加密测试(加密后的文件内容):
这里写图片描述
解密测试:
这里写图片描述

关于整整个从素数产生到选取、秘钥计算、加密解密实现的三个部分的代码,由于文件过多只贴出加解密功能代码,测试用的所有文件(随机数产生、密钥计算)可以下载:RSA密码(需要1下载积分,如果没有下载积分而需要源码的朋友也可以邮箱联系索取)。并且由于这里实现只是用unsigned long实现的比较小的数,大数测试可能不能正确解密。

加密解密:

/* *Function:Test for encryption and descryption *Author:Kangruojin *Mail:mailbox_krj@163.com *Time:2017年7月16日13:39:00 *Version:v1.2 **/#include "ende.h"int main(int argc, char * argv[]){    if(argc != 4){        ErrorOfInput(argv[0]);        return 0;    }    //注意:这里只是测试,这种类似与CBC的分组加密方式是不适合于RSA的    //RSA加密一般只有一组,即使有多组,也不应该采用CBC模式    if(0 == strcmp(argv[1],"-e")){        RsaEncrypt(argv[2], argv[3]);    }    else if(0 == strcmp(argv[1],"-d")){        RsaDecrypt(argv[2],argv[3]);    }    else{        ErrorOfInput(argv[0]);    }    return 0;}void ErrorOfInput(const char * str){    printf("请按格式输入运行:\n");    printf("%s -e 私钥e 私钥n\n",str);    printf("%s -d 公钥d 公钥n\n",str);}void RsaEncrypt(const char *argv2, const char *argv3){    huge_t ciphertext;    unsigned char plaintext;    RsaEK enk;    enk.e = (huge_t)atol(argv2);    enk.n = (huge_t)atol(argv3);    FILE * fpin = fopen("./message/plaintext.txt","rb");    FILE * fpout = fopen("./message/ciphertext.txt","wb");    assert(fpin != NULL);       assert(fpout != NULL);    fread(&plaintext, sizeof(plaintext), 1 ,fpin);//读取一个明文    while(!feof(fpin)){        //*ciphertext = plaintext^(enk->e) mod (enk->n)        ciphertext = ModExp((huge_t)plaintext, enk.e, enk.n);//加密一个字符        fwrite(&ciphertext, sizeof(ciphertext), 1, fpout);//写到秘文文件中        fread(&plaintext, sizeof(plaintext), 1 ,fpin);//读取下一个明文    }    printf("加密结束!\n");    fclose(fpin);    fclose(fpout);}void RsaDecrypt(const char *argv2, const char *argv3){    unsigned char plaintext,ciphertext;    RsaDK dnk;    dnk.d = (huge_t)atol(argv2);    dnk.n = (huge_t)atol(argv3);    FILE * fpin = fopen("./message/ciphertext.txt","rb");    FILE * fpout = fopen("./message/plaintextNew.txt","wb");    assert(fpin != NULL);       assert(fpout != NULL);    fread(&ciphertext, sizeof(ciphertext), 1 ,fpin);//读取一个秘文    while(!feof(fpin)){        //*plaintext = ciphertext^(dnk->d mod (dnk->n)        plaintext = ModExp((huge_t)ciphertext, dnk.d, dnk.n);//解密一个字符        fwrite(&plaintext, sizeof(plaintext), 1, fpout);//写到明文文件中        fseek(fpin, 3, SEEK_CUR);//加密文件中的四个字节中只有一个字节有效        fread(&ciphertext, sizeof(ciphertext), 1 ,fpin);//读取下一个秘文    }    printf("解密结束!\n");    fclose(fpin);    fclose(fpout);}const huge_t ModExp(huge_t msg, huge_t exp, huge_t mod){//平方乘算法    huge_t y =1;    while(exp != 0){        if(exp & 1){            y = (y*msg) % mod;        }        msg = (msg*msg)%mod;        exp = exp >> 1;    }    return y;}

参考资料推荐:
(1)、《应用密码学–协议算法与C源程序》[美]Bruce Schneier著 ,机械工业出版社【第19章:公开密钥算法】(RSA应用、性能、攻击分析等)。
(2)、《图解密码技术》[日]结城浩著,人民邮电出版社【第五章:公钥密码】(基本的图解流程)。
(3)、《算法导论》【第31章:数论算法】(基本的数论知识与RSA的指数运算的高效算法)。

原创粉丝点击