RSA加密算法解释与C++实现

最近因为一些原因对密码学产生了点兴趣,继之前用代码实现BASE64之后最近又搞起了RSA,这让我这个数学渣用从头开始学数学。。。。泪

RSA加密算法

RSA加密算法是由三位MIT大佬发现的,故RSA算法名称由来就是取他们三位名字i的首字母。
RSA算法是一种典型的不对称加密算法,说到不对称加密就会想到对称加密,在密码学加密算法大致可分为两种:对称加密与不对称加密。

对称加密

什么是对称加密,简单来讲就是加密与解密的密钥是相同的,举个简单的栗子,例如我们要对一个字符A加密,字符A即是明文,字符A的ASCII码为65,然后我们用一个十进制数10来做加密密钥,他的加密过程可表示为:密钥+明文,最后得出结果为75即字符K,这里的K即是密文,解密时只需要用相同的密钥10来进行逆运算:75-10就能得到明文,这就是一个最简单的对称加密的例子,当然真正的对称加密算法,过程与密钥不可能这么简单,对称加密代表算法:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK等。

不对称加密

不对称加密的栗子比较难举,没法用简单的例子来举,总之与对称加密的概念刚好相反,他会用到两个密钥,一个称为公钥,一个称为私钥,通常加密明文时用公钥进行加密,解密时必须要用私钥才能解密,用公钥无法进行解密,代表算法:RSA、Elgamal、背包算法、Rabin、D-H、ECC等。

比较

对称加密算法的优点一目了然:时间复杂度与空间复杂度相对于非对称加密都比较低,而缺点就是不够安全
非对称加密的优点:安全度高,缺点时间空间复杂度较高

这两种加密算法可以结合使用,就能达到相互补短的作用,比如用非对称加密加密密钥然后再用解密出来的密钥再对明文内容进行对称加密,常见https协议大致就用了这样的思路,这样即提高了安全性能又提高了传输速度与资源的占用

RSA算法实现过程

说完上面的前置知识,下面就着重解释RSA算法的实现过程,接下来我来分步骤进行解释:

  1. 首先我们需要找两个质数p、q
  2. 然后求出p、q的乘积n
  3. 然后再取一个欧拉函数φ(n)(欧拉函数百度百科),φ(n)=(q-1)*(p-1)
  4. 然后我们寻找一个公钥e,他要满足以下条件:
    1
    (e,φ(n))=1(公钥e要与φ(n)互质)
  5. 然后再去找到一个私钥d,他要满足的条件:
    (e*d)%φ(n)=1 (公钥私钥相乘除以φ(n),余数要为1
  6. 现在有了密钥,就该加密环节了,加密过程:
    假如我们有一个要发送的数据Y,一个公钥e,首先我们计算Y的e次方即Y^e得到一个数据,然后将这个得到的数据除以n,取得余数,这个取得的余数就是我们需要的密文y,计算过程可表示为:y=(Y^e)%n
  7. 现在还要知道如何解密,现在有一个密文y,还有一个私钥d,首先求y的d次方即y^d得到一个数,然后用这个数再除以n,得到的余数就是我们需要的明文Y,计算过程可简单表示为Y=(y^d)%n

然后我们再来看看这种算法能否被破解,假设现在有两端,服务端S客户端C,S与C之后的通信全部需要进行加密,首先S需要生成公钥与私钥,然后将公钥和一个数n传递给C,并且将私钥保存在本地,C 接收到公钥与n之后就会利用公钥与n来对自己的明文数据或消息进行加密,然后传递给S,S收到后用仅自己拥有的私钥进行解密,再拿到明文消息或数据。

破解方法

让我们康康,在整个传输过程中,作为hacker他能拿到的数据只有公钥e与数字n,他现在需要用这两个数来逆运算推导出私钥d,然后我们在来回头看看私钥d是如何算出来的(e*d)%φ(n)=1,现在我们只知道e与n如果想要再求d就必须要知道φ(n)那φ(n)又是如何计算的呢φ(n)=(q-1)*(p-1)现在我们又需知道p、q才能求出φ(n),p、q又怎样求,n=p*q,现在不难看出,如果n这个数比较小的话,也许的确有可能通过质数分解求出p、q但如果他是一个非常大的数呢?而RSA算法中常用的是1024比特位的二进制数,就目前技术而言无法将其分解,所以破解RSA加密算法理论上可行,但以目前被普及的技术来说还无法做到(或许、可能、大概、说不定、弄不好有天才数学大佬可以分解吧,但对于普通人来讲是不可能的)当然也有一个特例,那就是量子计算机,但普通人家里谁有量子计算机?

代码实现

实现代码前首先我们要解决几个问题:

  • 如何生成并存储一个1024位128个字节的高精度整数,首先用C语言自带的库很难完成这个工作,那只能考虑第三方库:GMP,这个库支持任意精度的大整数存储与运算
  • 如何得知公钥e与φ(n)互质,用辗转相除法,如果最大公约数为1即为互质,还有一个最快最简单的方法,那就是直接设为一个常用的数字65537即可

首先生成两个大质数:

	mpz_t key_p, key_q, temp_n;
    mpz_t fi, pub_key, pri_key;
    //初始化p,q,n,φ(n),公钥,私钥
    mpz_init(key_p);
    mpz_init(key_q);
    mpz_init(temp_n);
    mpz_init(fi);
    mpz_init(pub_key);
    mpz_init(pri_key);
    /*
    *生成随机1024位质数
    **/
   //随机数种子
    gmp_randstate_t grat;
    //默认生成在随机性与效率之间取一个折中
    gmp_randinit_default(grat);
    //以当前时间作为随机数种子
    gmp_randseed_ui(grat,time(NULL));
    //生成两个个1024位的随机整数
    mpz_urandomb(key_p,grat,1024);
    mpz_urandomb(key_q,grat,1024);
    //生成素数
    mpz_nextprime(key_p,key_p);
    mpz_nextprime(key_q,key_q);

输出结果为:

60317106189242150968029907905478884454926397838
63151192056069393976678696526755735882204871919
60640863409260125737784699838551404308288285427
22202009629439178701380058086593614241889585723
84631477965807444301047539221743520620231455186
51441389685261033128949964706605835556481358609
98173250327588025985739839

66011711808975252037545823728364010624488907689
90643069363083107799625584556500033745996473231
15790980352676928009148324711181846268064324503
83413178315138572996883354239248096907001626608
08392625765952133578008721528020700996465748851
57917174906323291939335096453676892850856698552
86464761830290932578868973

现在我们去计算n与φ(n):

	/*
    *获得n与φ(n)
    **/
    //p×q得到n
    mpz_mul(temp_n,key_q,key_p);
    //p,q分别减一后相乘得到φ(n),
    //注意结尾的ui表示32位无符号整数
    mpz_sub_ui(key_p,key_p,1);
    mpz_sub_ui(key_q,key_q,1);
    mpz_mul(fi,key_q,key_p);

公钥与私钥获取:

    /*得到公钥e此值可取65537、17、37、47
    *,但要注意,此值除65537以外其他小值
    *做公钥得到的密文是固定不变的,也就是说安全性是不可靠的
    **/
    mpz_set_ui(pub_key,65537);
       
    //逆元运算,求私钥
    mpz_invert(pri_key,pub_key,fi);
    //将公钥私钥与n化为字符串
    mpz_class temp_d(pri_key);
    mpz_class swap_n(temp_n);
    public_key=temp_e.get_str();
    private_key=temp_d.get_str();
    n=swap_n.get_str();

输出结果:

n:
2613885932641755784748607510882050915421352894700858986137412842
0987630493825747297211771889610932122550166024444213136746894207
6644546811985416050557640881973619678619059413712496525559970627
8043143184219087235268656120138267882121069707247964843912512433
8774988087150761110187943552230597253550915681571836928742533421
1054867862933657928417604997759982264669709738037817960310557790
0425911136950171751039667170480765334133554853043160545583229033
5211860578895411731343717290527769002490146989259962541984077360
5698861639713299078187576869256147022559692757370637103995863292
86924700157128795434021572801287758658571
public_key:
65537
private_key:
3988412549615874673464771824895938043275329805607304249717583719
2711949728894742355023531576988467770191137867836814527285188836
3282644631254735570071319837608709093518255967945582686970674012
8542873772401982445440981613650713157637776686830286470104692668
0768097543602485786941641442590593486962960894718764369662545691
8353551197615893923404896499744348438188986738562549978291283277
6902507986697558350861043057925054330262762226384991381287155378
5664270488274091860994402711170314547507999860361506479173635101
8119034090998956073490414694527919620672184154802411523118163683
351078996272709037151947654017045651

我将获取e、d、n的代码单独写成了一个类的成员函数,完整代码:

void RSA::getKey
(
    _Inout_ string& public_key,
    _Inout_ string& private_key,
    _Inout_ string& n
)
{
    mpz_t key_p, key_q, temp_n;
    mpz_t fi, pub_key, pri_key;
    //初始化p,q,n,φ(n),公钥,私钥
    mpz_init(key_p);
    mpz_init(key_q);
    mpz_init(temp_n);
    mpz_init(fi);
    mpz_init(pub_key);
    mpz_init(pri_key);
    /*
    *生成随机1024位质数
    **/
   //随机数种子
    gmp_randstate_t grat;
    //默认生成在随机性与效率之间取一个折中
    gmp_randinit_default(grat);
    //以当前时间作为随机数种子
    gmp_randseed_ui(grat,time(NULL));
    //生成两个个1024位的随机整数
    mpz_urandomb(key_p,grat,1024);
    mpz_urandomb(key_q,grat,1024);
    //生成素数
    mpz_nextprime(key_p,key_p);
    mpz_nextprime(key_q,key_q);
    /*
    *获得n与φ(n)
    **/
    //p×q得到n
    mpz_mul(temp_n,key_q,key_p);
    //p,q分别减一后相乘得到φ(n),
    //注意结尾的ui表示32位无符号整数
    mpz_sub_ui(key_p,key_p,1);
    mpz_sub_ui(key_q,key_q,1);
    mpz_mul(fi,key_q,key_p);
    /*得到公钥e此值可取65537、17、37、47
    *,但要注意,此值除65537以外其他小值
    *做公钥得到的密文是固定不变的,也就是说安全性是不可靠的
    **/
    mpz_set_ui(pub_key,65537);

    //逆元运算
    mpz_invert(pri_key,pub_key,fi);
    //将公钥私钥与n化为字符串
    mpz_class temp_d(pri_key);
    mpz_class swap_n(temp_n);
    mpz_class temp_e(pub_key);
    public_key=temp_e.get_str();
    private_key=temp_d.get_str();
    n=swap_n.get_str();

    mpz_clear(key_p);
    mpz_clear(key_q);
    mpz_clear(temp_n);
    mpz_clear(fi);
    mpz_clear(pub_key);
    mpz_clear(pri_key);
}

_Inout_是我自定义的一个空宏起说明作用,个人习惯不用管他

然后,便是加解密函数,代码比较少,也比较简单,直接用GMP中的模幂函数来求即可:

加密:

string RSA::RSA_Encode
(
    _In_ const char* IN_Data,
     _Inout_ size_t& inoutLen,
    _In_ string public_key,
    _In_ string n
)
{
    mpz_t m,pub_e,temp_n;
    mpz_init(m);
    mpz_init(pub_e);
    mpz_init(temp_n);
    //取得公钥、n、明文,将输入内容统一转化为十进制高精度整数
    mpz_set_str(pub_e,public_key.c_str(),10);
    mpz_set_str(temp_n,n.c_str(),10);
    string out_data;
    /*对字符串循环加密,并用回车隔开
    *防止字符串密文混乱无法辨识
    **/
    for(int i=0;i<inoutLen;i++)
    {
        mpz_set_ui(m,(unsigned long)IN_Data[i]);
        /*
        *模幂操作,取密文
        *c=(m^e) mod n
        **/
        mpz_powm(m,m,pub_e,temp_n);
        //取得字符串
        mpz_class c_data(m);
        out_data.append(c_data.get_str());
        out_data.append("\n");
    }
    inoutLen=out_data.length();

    mpz_clear(m);
    mpz_clear(pub_e);
    mpz_clear(temp_n);
    return out_data;
}

为了解决字符串字符加密后密文存放在一起,无法分清哪个密文属于哪个明文问题,我们采用了使用回车将密文隔开的办法,那么在解密过程中就需要将密文字符串拆解为一个字符串列表来依次处理
解密:

string RSA::RSA_Decode
(
    _In_ string private_key,
    _In_ string n,
    _In_ string c_data,
    _Inout_ size_t& inoutLen
)
{
    vector<string>C_List;
    string temp_str;
    cout<<"\n\n\n\n\n\n";
    //循环拆分字符串,根据原有字符个数拆分为字符串容器列表
    //以此按顺序处理密文
    for(int i=0;i<inoutLen;i++)
    {
        if(c_data.at(i)=='\n')
        {
            C_List.push_back(temp_str);
            temp_str.clear();
            continue;
        }
        temp_str+=c_data.at(i);
    }
    //cout<<"list\n\n"<
    
    mpz_t pri_key,temp_n,C_Data;
    mpz_init(pri_key);
    mpz_init(temp_n);
    mpz_init(C_Data);
    //将字符串转为mpz_t高精度整数
    mpz_set_str(pri_key,private_key.c_str(),10);
    mpz_set_str(temp_n,n.c_str(),10);
    string back_data;
    //循环根据容器个数来判断原本有几个字符,
    //并将其密文解析为明文
    for(int i=0;i<C_List.size();i++)
    {
    	//从字符串取值转化为十进制大整数
        mpz_set_str(C_Data,C_List.at(i).c_str(),10);
        //模幂运算M=(C^d) mod n
        mpz_powm(C_Data,C_Data,pri_key,temp_n);
        //先将取到的明文ASCII转化为C语言基本类型
        //再直接将ASCII码转为字符,再将字符追加进字符串
        //这样就完美还原了明文
        mpz_class CD(C_Data);
        unsigned long lchar=CD.get_ui();
        char words=(char)lchar;
        back_data+=words;
    }
    //返回长度
    inoutLen=back_data.length();
    mpz_clear(pri_key);
    mpz_clear(temp_n);
    mpz_clear(C_Data);
    //返回明文
    return back_data;
}

然后再写一个调试代码:

#include"RSA.h"

int main()
{
    string e,d,n;
    RSA* a=new RSA();
    a->getKey(e,d,n);
    size_t out_len=strlen("啦啦啦");
    string c=a->RSA_Encode("啦啦啦",out_len,e,n);
    cout<<"\n明文:\n啦啦啦\n"<<"密文:\n"<<c<<"\n长度:\n"<<out_len;
    string m=a->RSA_Decode(d,n,c,out_len);
    cout<<"\n\n密文:\n"<<c<<"\n明文:\n"<<m<<"\n长度:\n"<<out_len;
    return 0;
}

加密结果:

解密结果:

中文加密没有问题英文加密测试过也是没有问题,所以从上图可以看出仅三个中文字符6个字节的数据就会获得如此庞大的密文,所以不对称加密在传输过程中是极其耗费带宽与时间的,所以并不适合某些高频率传输的场景

完整代码已上传git,地址:
github

你可能感兴趣的:(C/C++,小菜鸡,渗透,密码学,算法,数据结构,linux,github)