2019.11.23:
这个代码一开始采用的是如《算法笔记》上等PAT中的类似题目将十进制数字转换为数组大整数(也是十进制形式)。
但是这种想法在后来实现加密的时候快速积出现了严重的弊端,和普通十进制并没有什么区别了,依旧拖慢进程大的可怕。
今天课设答辩同学们都做出了像毕设一样的作品,很完善的调用各种类实现各种混合加密,还有图形化界面,python、java、各种语言起飞。到了我这,可怜的连个运行起来都有问题。
下面的代码是256进制的刚起步,老师说不要这样转换,可以直接用long int类型读入(C++),然后直接转换为256进制。
我在下面的写法没有抛弃原来的10进制大整数。
#include <iostream>
#include <string>
#include<algorithm>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
/*1.生成密钥对,(e,d,n),公钥是(e,n),私钥是(d,n)
步骤如下:
求n(准备两个质数p,q。这两个数不能太小,太小则会容易破解,将p乘以q就是N),
求f=(p-1)(q-1)
求e :gcd(e,f)=1
求d:e*d mod f = 1
2.加密:用(e,n):密文 c=m^e(mod n)
3.解密:用(d,n):明文 m=c^d(mod n)
*/
//定义大整数结构体,使用保存十位数字每一位的方法
struct bigint{
int d[1000];
int len;
bigint(){
memset(d,0,sizeof(d));
len=0;
}
};
bigint e,d,p,q,n;
struct bigint256{
int d[32];
int len;
bigint256(){
memset(d,0,sizeof(d));
len=0;
}
};
int compare(bigint a,bigint b){//比较a和b的大小,a大、相等、a小分别返回1,0,-1
if(a.len>b.len)return 1;//a大
else if(a.len<b.len)return -1;//a小
else{
for(int i=a.len-1;i>=0;i--){
if(a.d[i]>b.d[i]) return 1;//只要有一位a大,则a大
else if(a.d[i]<b.d[i])return -1;//只要有一位a小,则a小
}
return 0;
}
}
void print(bigint a){
for(int i=a.len-1;i>=0;i--){
cout<<a.d[i];
}
}
//出错的原因:求解exgcd没有成功,是因为没有定义负数的运算!==
//重载大整数的运算
//进制转换需要转换为256进制
bigint change(char str[]){
bigint a;
a.len=strlen(str);
for(int i=0;i<a.len;i++){
a.d[i]=str[a.len-i-1]-'0';
}
return a;
}
char s0[3]="0";
char s1[3]="1";
char s2[3]="2";
char s3[3]="3";
bigint in0=change(s0);
bigint in1=change(s1);
bigint in2=change(s2);
bigint in3=change(s3);
//重载大整数减法
bigint operator - (bigint a, bigint b)
{
bigint temp;
int sign=1;
if(compare(a,b)<0){
temp=a;
a=b;
b=temp;
sign=-1;
}
//cout<<"a:";print(a);cout<<endl;
//cout<<"b:";print(b);cout<<endl;
bigint c;
int carry = 0;//carry是进位
for(int i=0;i<a.len||i<b.len;i++){//以较长的为界限
if(a.d[i]<b.d[i]){
a.d[i+1]--;//向高位借位
a.d[i]+=10;
}
c.d[c.len++]=a.d[i]-b.d[i];//减法结果为当前借位结果
}
while(c.len-1>=1&&c.d[c.len-1]==0){
c.len--;//去除高位的0,同时至少保留1位最低位
}
if(sign==1)return c;
else if(sign==-1){
c.d[c.len-1]=-c.d[c.len-1];
return c;
}
}
//重载高精度加法
bigint operator + ( bigint a, bigint b)
{
if(a.d[a.len-1]<0&&b.d[b.len-1]>0){
a.d[a.len-1]=-a.d[a.len-1];
return b-a;
}
if(a.d[a.len-1]>0&&b.d[b.len-1]<0){
b.d[b.len-1]=-b.d[b.len-1];
return a-b;
}
int sign=1;
if(a.d[a.len-1]<0&&b.d[b.len-1]<0){
b.d[b.len-1]=-b.d[b.len-1];
a.d[a.len-1]=-a.d[a.len-1];
sign=-1;
}
bigint c;
int carry = 0;//carry是进位
for(int i=0;i<a.len||i<b.len;i++){
int temp=a.d[i]+b.d[i]+carry;//两个对应位与进位相加
c.d[c.len++]=temp%10;
carry=temp/10;//十位数为新的进位
}
if(carry!=0){
c.d[c.len++]=carry;
}
while(c.len-1>=1&&c.d[c.len-1]==0){//清高位零
c.len--;}
if(sign==1)return c;
else if(sign==-1){
c.d[c.len-1]=-c.d[c.len-1];
return c;
}
}
//重载大整数除法
/*
思想:反复做减法,看从被除数里面最多能减去多少个除数,商就是多少。
*/
//重新写一个减法,适合于返回长度,将数组直接做减法,参数是bigint中数组
int SubStract( int *p1, int *p2, int len1, int len2 )
{
int i;
if( len1 < len2 )
return -1;
if( len1 == len2 )
{ //判断p1 > p2
for( i=len1-1; i>=0; i-- )
{
if( p1[i] > p2[i] ) //若大,则满足条件,可做减法
break;
else if( p1[i] < p2[i] ) //否则返回-1
return -1;
}
}
for( i=0; i<=len1-1; i++ ) //从低位开始做减法
{
p1[i] -= p2[i];
if( p1[i] < 0 ) //若p1<0,则需要借位
{
p1[i] += 10; //借1当10
p1[i+1]--; //高位减1
}
}
for( i=len1-1; i>=0; i-- ) //查找结果的最高位
if( p1[i] ) //最高位第一个不为0
return (i+1); //得到位数并返回
return 0; //两数相等的时候返回0
}
bigint operator / (bigint a, bigint b){//用一个例子方便写代码7546/23=328
bigint c,z;
if(a.len<b.len){
return in0;
}
int len=a.len-b.len;//记录大整数a和大整数b的位数的差,方便下面给被除数b在低位补0,对齐a。len=2
for(int i=a.len-1;i>=0;i--){
//首先将原来位置的b的数字移到后边(高位)
if(i>=len){
b.d[i]=b.d[i-len];//与a的长度对齐,现在即是xx32 (数组是从低位到高位进行存储)
}
//如果差值到了,开始补零
else{
b.d[i]=0;
}
}
b.len=a.len;//现在b的长度和a的长度一样了,b[]="0032"即大数2300
//现在可以利用上面的不断做减法记录次数计算除法的结果了。可以知道商的位数最大是len+1,即差值+1。
//所以外层循环是0-len,内层循环对商的每一位进行计数
bigint a1=a;//为了防止改变a,新增一个变量a1,变为a的副本
for(int j=0;j<=len;j++)
{
//需要更新外层循环中b的长度,如第一次7546-2300*3=646,第二次646-230*2=186```,每次减少1
//注意b.len-1即因为运算是从低位开始“0032”即从下标0开始,
//所以在新的while循环中指针b.d要从下一位开始,即“032”,长度相应的当然要减少一个
while((c.len=SubStract(a1.d,b.d+j,a1.len,b.len-j))>=0){//对齐后如果减1次结果还是大于等于0,说明没有减净
a1.len=c.len;//更新a1的长度,即被除数的长度为结果的长度,
//如7532-2300-2300-2300=632,这个时候a1.d="236",因为SubStract函数将结果保存在了a1.d中,所以被除数自动更新,但是位数要自己手动
z.d[len-j]++;//开始保存商的结果,接着进行减法
//cout<<"运算过程:z.d[len-j]"<<"z.d["<<len<<"-"<<j<<"]="<<z.d[len-j]<<endl;
}
z.len++;
}
//错误:for( z.len;z.d[z.len]==0 && z.len>=0;z.len--);//求出的商可能跳过高位有0,所以再重新更新商的长度,如可能求出0030,把后面多余的0去掉
//cout<<"z.len"<<z.len<<endl;
while(z.len-1>=1&&z.d[z.len-1]==0){
z.len--;
}
//cout<<"z.len"<<z.len<<endl;
return z;
}
//重载大整数乘法
/*
列竖式找规律
如
1 2 5
X 5 3
先不要着急进位
1)125*3即 5*3 *1 + 2*3 *10 + 1*3 *100 = 15 *1 + 6 *10 + 3 *100
2)125*5即5*5 *10 + 2*5 *100 + 1*5 *1000 = 25 *10 + 10 *100 + 5 *1000
最后结果是 :a[0]->15*1进位1留5,a[1]->6+25+1=32 进位3留2,a[2]->3+10+3=16 进位1 留6,a[3]->5+1=6无进位 =>6625
找到规律ans[i+j]=a[i]+a[j];
*/
bigint operator * (bigint a, bigint b){//用一个例子方便写代码7546/23=328
bigint z;
z.len=a.len+b.len;
for(int i=0;i<a.len;i++)//将因数各个位上的数字与另一个各个位上的数字相乘
{
for(int j=0;j<b.len;j++)
z.d[i+j]=z.d[i+j]+a.d[i]*b.d[j];//先乘起来,后面统一进行进位
}
for(int i=0;i<z.len;i++)//进行进位
{
if(z.d[i]>=10) //若>=10
{
z.d[i+1]=z.d[i+1]+z.d[i]/10; //将十位上数字进位
z.d[i]=z.d[i]%10; //将个位上的数字留下
}
}
while(z.len-1>=1&&z.d[z.len-1]==0){//和上面一样清零
z.len--;
//cout<<"a1.len--;a1.len="<<a1.len<<endl;
}
return z;
}
//重载大整数取模运算
//5%96?
bigint operator % (bigint a, bigint b){//用一个例子方便写代码7546/23=328
bigint divTmp = a / b ;
return (a - b * divTmp);
}
//===256==========256==================256===================
/*
转换的思想:
让bigint不断的除以256,采用除基取余的方法。
int z[40],num=0;//数组z存放Q进制y的每一位数,num为位数。
do{
z[num++]=y%Q;//模基取余法
y=y/Q;
}while(y!=0)//当商不为0时,进行循环。
*/
void printb(bigint256 bg)
{
cout<<"打印256进制大整数"<<endl;
for(int i=bg.len-1;i>=0;i--)
{
cout<<bg.d[i]<<",";
}
}
char str256[4]="256";
bigint in256=change(str256);
bigint256 change256(bigint y){
bigint256 z;
z.len=0;
cout<<" print(y):"; print(y);cout<<endl;//测试
bigint temp;
do{
temp=y%in256;
cout<<" print(temp):"; print(temp);cout<<endl;//测试
//?如何将求模运算的结果保存在一个数组中,因为可能是0-3位数字。要存储在z.d[]的一个数值中
//整型数组恢复正常十进制即可,a[0]*1+a[1]*10+a[2]*100
for(int i=temp.len-1;i>=0;i--)
{
//只能先正序存,后面再逆过来
z.d[z.len]+=temp.d[i]*pow(10.0,(double)d.len-i);
// cout<<" printb(z):"; printb(z);cout<<endl;//测试
}
z.len++;
y=y/in256;
}while(compare(y,in0)!=0);
int tt;
for(int i=0;i<z.len-1;i++)
{
tt=z.d[i];
z.d[i]=z.d[z.len-i-1];
z.d[z.len-i-1]=tt;
}
cout<<" printb(z):"; printb(z);cout<<endl;//测试
return z;
}
//重载完了大整数的基本运算后,来实现生成两个大整数P,Q(质数)
//自定义随机数发生器
bigint randb(int len){//输入十进制想要生成的质数的位数,注意1024bit是128字节即128位
bigint a;
a.len=len;
for(int i=0;i<len;i++){
a.d[i]=rand()/10;//使用rand()函数产生0-9以内的随机整数
}
return a;
}
//为了方便,重载==
bool operator ==(bigint a,bigint b){
if(compare(a,b)==0)return 1;
else if(compare(a,b)==-1) return -1;
else if(compare(a,b)==1) return -1;
}
//准备开始miller-Rabin算法
bigint Quick_Multiply(bigint a,bigint b,bigint mod) //快速积(和快速幂差不多)
{
bigint ans=in0,res=a;
while(compare(b,in0)!=0)//由于我没有重载比较运算符,但是在前面定义了compare函数,所以在这如果b<0则停止,应该是一样的。
{
if(b%in2==in1)
ans=(ans+res)%mod;
res=(res+res)%mod;
// b>>=1;//右移相当于除以2
b=b/in2;
}
while(ans.len-1>=1&&ans.d[ans.len-1]==0){//清高位零
ans.len--;}
return ans;
}
bigint Quick_Power(bigint a,bigint b,bigint c) //快速幂
{
bigint ans=in1,res=a;
//if(b==in0)return in1;
while(compare(b,in0)>0)//由于我没有重载比较运算符,但是在前面定义了compare函数,所以在这b=0则停止,应该是一样的。
{
//if(b&1)//b&1即b%2==1
if(b%in2==in1){
ans=ans*a%c;
}
a=a*a%c;
// b>>=1;//右移相当于除以2
b=b/in2;
}
while(ans.len-1>=1&&ans.d[ans.len-1]==0){//清高位零
ans.len--;}
return ans;
}
bool Miller_Rabin(bigint x) //判断素数
{
//我们可以多选择几个 a,如果全部通过,那么 x 大概率是质数。
char s5[3]="5";
char s7[3]="7";
char s19[3]="19";
char s23[3]="23";
bigint in5=change(s5);
bigint in7=change(s7);
bigint in19=change(s19);
bigint in23=change(s23);
char s11[3]="11";
char s13[3]="13";
char s17[3]="17";
char s29[3]="29";
bigint in11=change(s11);
bigint in13=change(s13);
bigint in17=change(s17);
bigint in29=change(s29);
bigint prime[10]={in2,in3,in5,in7,in11,in13,in17,in19,in23,in29};
bigint k;
bigint s=in0,t=x-in1,j;
if(x==in2) return true; //2是素数
// if(x<2||!(x&1)) return false; //如果x是偶数或者是0,1,那它不是素数
if(compare(x,in2)<0||(x%in2==in1)) return false;
// while(!(t&1)) //将x分解成(2^s)*t的样子
while(t%in2==in1)
{
s=s+in1;
// t>>=1;
t=t/in2;
}
// for(i=0;i<10&&prime[i]<x;++i) //随便选一个素数进行测试
for(int i=0;i<10&&(compare(prime[i],x)<0);++i)
{
bigint a=prime[i];
bigint b=Quick_Power(a,t,x); //先算出a^t
//for(j=1;j<=s;++j) //然后进行s次平方
for(j=in1;compare(j,s)<=0;j=j+in1)
{
k=Quick_Multiply(b,b,x); //求b的平方
// if(k==in1&&b!=1&&b!=x-1) //用二次探测判断
if(k==in1&&compare(b,in1)!=0&&compare(b,x-in1))
return false;
b=k;
}
//if(b!=1) return false; //用费马小定律判断
if(compare(b,in1)!=0) return false;
}
return true; //如果进行多次测试都是对的,那么x就很有可能是素数
}
//1.随机生成两个质数p,q
bigint genPrime(int len)
{
bigint res = randb(len);
// Print(res);
long loop = 20;
long count = 0;
while(!Miller_Rabin(res))
{
res = randb(len);
// Print(res);
count ++;
}
// cout<<count<<endl;
while(res.len-1>=1&&res.d[res.len-1]==0){//清高位零
res.len--;}
return res;
}
/*
RSA中的e不能太小同样是密码学家D. Coppersmith,其于1990年指出[3]:
用于RSA的指数e不能太小,否则存在快速算法计算得到私钥d。这个定理要用到格密码学的著名算法LLL。
再次就不多做展开了… 不过需要注意的是,由于加密算法要计算m^e,如果e太大的话加密过程可能会比较慢。
现在一般认为令e=65537是比较合适的。
https://www.zhihu.com/question/54779059?sort=created
*/
bigint ex_gcd(bigint a,bigint b,bigint &x,bigint &y) //扩展欧几里得
{
//cout<<"compare(b,in0):"<<compare(b,in0);cout<<endl;
if(compare(b,in0)==0)
{
x=in1;
y=in0;
//cout<<"b==in0"<<endl;
return a;
}
/*cout<<"b!=in0"<<endl;
cout<<"a:"<<endl;print(a);cout<<endl;
cout<<"b:"<<endl;print(b);cout<<endl;
cout<<"x:"<<endl;print(x);cout<<endl;
cout<<"y:"<<endl;print(y);cout<<endl;cout<<endl;
*/
//bigint chu=a%b;
bigint r=ex_gcd(b,a%b,x,y);
bigint t=x;
x=y;
y=t-(a/b*y);//大整数的减法我没有定义如果结果为负数怎么办。
return r;
}
bigint mod_reverse(bigint a,bigint n)//ax=1(mod n) 求a的逆元x
{
bigint d,x,y;
d=ex_gcd(a,n,x,y);
if(d==in1){
//cout<<"d==in1"<<endl;
x=x+n;
//cout<<"x=x+n;"<<endl;print(x);cout<<endl;
return (x%n+n)%n;
}
else
return in0;
}
bigint Get_e(bigint p,bigint q,bigint &x,bigint &y)
{
char stre1331[5]="1331";
bigint in1331=change(stre1331);
char stre65537[6]="65537";//备用
bigint in65537=change(stre65537);
//用欧几里德扩展算法求e,d。 随机选择一个整数 e,条件是1< e < f,且 e 与 f互质。
bigint f =(p-in1)*(q-in1);
//cout<<"\nf"<<endl;print(f);
cout<<endl;
//for(bigint j = in3; j < f; j+=1331)
for(bigint j = in65537;compare(j,f)<0; j=j+in1331)//这里应该是挨个找,正好符合gcd(e,f)=1的时候,得到e。???这里出现问题!e怎么生成不了
{
bigint gcd=ex_gcd(j,f,x,y);
//if( (gcd==1)&& x> 0)
if( (compare(gcd,in1)==0)&& compare(x,in0)>0)
{
e = j;
break;
}
}
return e;//d为x.直接使用扩展欧几里得求出x后就可以用(x%m+m)%m得到(0,m)范围内的解。
}
char tmp[10000];
int readMyf(){
FILE *fp;
fp = fopen("D://日常//大学文件//大三上学期//学科//安全协议//实验//MyCry02//mingwen.txt" , "r");
fseek( fp , 0 , SEEK_END );
int file_size;
file_size = ftell( fp );
cout<<"读取到文件大小为: "<< file_size<<endl;
//ifstream file("filename");//D://日常//大学文件//大三上学期//学科//安全协议//实验//MyCry02//mingwen.txt
fseek( fp , 0 , SEEK_SET);
fread( tmp , file_size , sizeof(char) , fp);
tmp[file_size] = '\0';
fclose(fp);
cout<<"文件内容为 : "<< tmp <<endl;
return file_size;
}
bigint code;
bigint message;
void RSA_Encrypt(){
//对明文加密c=m^e(modn) 用前面的大整数幂运算Quick_Power(m,e)即可
//首先从本地读取明文
int file_size;
file_size=readMyf();//读取到文件
//获取数组tmp[file_size]
bigint bigming=change(tmp);//转换为bigint类型。
cout<<"转换为bigint类型"<<endl;print(bigming);cout<<endl;
//注意:这里有很大的缺陷,对于空格和英文字母及大小写,需要重新写函数进行转换和映射才可以,时间有限这里待完善
code=Quick_Power(bigming,e,n);
cout<<"加密后的文件内容为:"<<endl;
print(code);cout<<endl;
//cout<<str<<endl;
}
void RSA_Decrypt(){
//解密:m=c^d(mod n)
message=Quick_Power(code,d,n);
cout<<"解密后的文件内容为:"<<endl;
print(message);cout<<endl;
}
void initskey()//生成p,q,e,d,初始化密钥
{
p= genPrime(56);//选择生成1024bit的p和q,2^N有N*log2位数,2^1024=1.675*10^308;(是309位数)
q= genPrime(56);
cout<<"生成大素数p,q"<<endl;
cout<<"p:";print(p);
cout<<"\nq:";print(q);
bigint f1=p-in1;
bigint f2=q-in1;
bigint f=f1*f2;//Stack around the variable 'f' was corrupted出现错误:怎么修改:project->配置属性->c/c++->代码生成->基本运行时检查 设置为默认值
char stre65537[6]="65537";//备用
bigint in65537=change(stre65537);
cout<<"\n随机生成私钥e"<<endl;
bigint x,y;
e=Get_e(p,q,x,y);
cout<<"--------》e:";print(e);
cout<<"\n生成公钥d"<<endl;
d=mod_reverse(e,f);
cout<<"d:";print(d);
n=p*q;
}
//hash函数,将需要传送的信息hash成大整数,然后进行运算加密。?这里卡壳了,hash好像不可逆,是用于签名的。那这里直接对消息进行加密?全部转换成ASCII码?
int main()
{
/*char str1[1000],str2[1000];
cin>>str1>>str2;
bigint num1=change(str1);
bigint num2=change(str2);
//测试加法
bigint sum=num1+num2;
cout<<"\n****测试大整数加法结果********"<<endl;
print(sum);
cout<<"************"<<endl;
//测试减法
bigint sub=num1-num2;
cout<<"\n****测试大整数减法结果********"<<endl;
print(sub);
//测试大整数除法
bigint mul=num1/num2;
cout<<"\n****测试大整数除法结果********"<<endl;
print(mul);
//测试大整数取模
bigint mod=num1%num2;
cout<<"\n****测试大整数取模结果********"<<endl;
print(mod);
//测试大整数乘法
bigint multi=num1*num2;
cout<<"\n****测试大整数乘法结果********"<<endl;
print(multi);
//测试扩展欧几里得求逆元
char strd1[3]="5";
char strd2[6]="96";
bigint d1;
bigint numd1=change(strd1);
bigint numd2=change(strd2);
d1=mod_reverse(numd1,numd2);
cout<<"\n****测试扩展欧几里得求逆元5*e mod96结果********"<<endl;
cout<<"d:";print(d1);
//
cout<<"\n****测试读取文件结果********"<<endl;
readMyf();
cout<<"\n****测试Quick_Power结果********"<<endl;
cout<<"print(Quick_Power(in2,in2,in3));"<<endl;
print(Quick_Power(in2,in2,in3));cout<<endl;
//生成大素数
cout<<"\n****测试生成大素数结果********"<<endl;
cout<<"生成大素数:"<<endl;
bigint p1 = genPrime(128);
cout<<"p=";print(p1);
char str5[2]="5";
bigint in5=change(str5);
//cout<<"5%1:";print(in5%in1);cout<<endl;
//测试密钥生成
initskey();
RSA_Encrypt();
RSA_Decrypt();
//测试扩展欧几里得算法,用e=5,d=96
/*bigint in5;
in5.len=1;
in5.d[in5.len-1]=5;
bigint in96;
in96.len=2;
in96.d[in5.len-1]=9;
in96.d[in5.len-2]=6;
bigint ed,ey;
//Get_e(in5,in96,ed,ey);
ed=(ed%in96+in96)%in96;
cout<<"d"<<endl;
print(ed);
*/
//测试256进制转换
char str[6]="65537";
bigint in65537=change(str);
bigint256 bin=change256(in65537);
printb(bin);
return 0;
}
2020.1.9:后记:这个后来用了1024bit的大整数,二进制什么的,现在我已经忘光了,记得当时网上有人家写的很好的,然后我从头到尾开始分析他代码的思想,做了个分析,然后加工加工就写报告了。
小结:关于大学里面课设的话,可能外观更重要,算法做的再好,以我现在菜鸡的水平也做不了什么,所以搞个壳子是最重要的,有了壳子之后里面的算法网上都有。
希望以后做毕设可以顺利些。